[ux_html]
Học C#
Cho Dân IoT
Biến, kiểu dữ liệu và nhập/xuất Console
Cơ bản
▾
Khai báo biến các kiểu cơ bản, nhập dữ liệu từ bàn phím và in ra thông tin thiết bị IoT. Đây là bước đầu tiên khi viết ứng dụng giám sát.
using System;
class DeviceInfo
{
static void Main()
{
// Khai báo thông tin thiết bị
string deviceName = "Sensor-01";
int deviceId = 101;
double temperature = 36.5;
bool isOnline = true;
Console.WriteLine("=== Thông tin thiết bị IoT ===");
Console.WriteLine($"Tên : {deviceName}");
Console.WriteLine($"ID : {deviceId}");
Console.WriteLine($"Nhiệt độ: {temperature:F1} °C");
Console.WriteLine($"Online : {isOnline}");
// Nhập từ người dùng
Console.Write("nNhập nhiệt độ mới: ");
double newTemp = double.Parse(Console.ReadLine()!);
Console.WriteLine($"Đã cập nhật: {newTemp:F1} °C");
}
}
Tên : Sensor-01
ID : 101
Nhiệt độ: 36.5 °C
Online : True
Nhập nhiệt độ mới: 38.2
Đã cập nhật: 38.2 °C
Câu lệnh điều kiện – Cảnh báo ngưỡng cảm biến
Cơ bản
▾
Dùng if/else if/else để phân loại cảnh báo theo mức nhiệt độ: bình thường, cảnh báo, nguy hiểm. Thực tế dùng trong alarm management của HMI/SCADA.
using System;
class AlarmCheck
{
static string GetAlarmLevel(double temp)
{
if (temp < 0 || temp > 100)
return "❌ LỖI CẢM BIẾN";
else if (temp > 80)
return "🔴 NGUY HIỂM";
else if (temp > 60)
return "🟡 CẢNH BÁO";
else
return "🟢 BÌNH THƯỜNG";
}
static void Main()
{
double[] readings = { 25.0, 65.3, 85.7, -5.0 };
foreach (double t in readings)
{
string level = GetAlarmLevel(t);
Console.WriteLine($" {t,6:F1}°C → {level}");
}
}
}
65.3°C → 🟡 CẢNH BÁO
85.7°C → 🔴 NGUY HIỂM
-5.0°C → ❌ LỖI CẢM BIẾN
Vòng lặp – Mô phỏng chu kỳ đọc cảm biến
Cơ bản
▾
Dùng for và while để mô phỏng vòng lặp đọc dữ liệu định kỳ — giống polling loop trong lập trình nhúng và IoT gateway.
using System;
class SensorPolling
{
static void Main()
{
var rng = new Random(42); // seed cố định để tái lập
Console.WriteLine("Chu kỳ đọc cảm biến (mỗi 500ms):");
Console.WriteLine("Tick Nhiệt(°C) Độ ẩm(%) Áp suất(bar)");
Console.WriteLine(new string('-', 42));
for (int tick = 1; tick <= 6; tick++)
{
double temp = 22 + rng.NextDouble() * 8;
double humidity = 55 + rng.NextDouble() * 20;
double pressure = 1.00 + rng.NextDouble() * 0.1;
Console.WriteLine($" {tick,3} {temp,7:F2} {humidity,7:F1} {pressure,8:F3}");
}
Console.WriteLine("nHoàn tất thu thập dữ liệu.");
}
}
Tick Nhiệt(°C) Độ ẩm(%) Áp suất(bar)
——————————————
1 26.06 68.2 1.037
2 28.74 62.8 1.091
3 23.15 71.4 1.014
4 25.80 59.3 1.058
5 27.33 67.0 1.003
6 22.91 74.8 1.076
Hoàn tất thu thập dữ liệu.
Mảng và List – Lưu trữ lịch sử đo lường
Cơ bản
▾
Sử dụng List<double> để lưu lịch sử đo và tính toán min/max/trung bình — thao tác phổ biến trong data logging của hệ thống IoT.
using System;
using System.Collections.Generic;
using System.Linq;
class DataLogger
{
static void Main()
{
var history = new List<double>
{
24.1, 25.3, 27.8, 26.4, 28.2,
30.5, 29.1, 27.6, 25.9, 24.7
};
Console.WriteLine("📊 Báo cáo nhiệt độ hôm nay:");
Console.WriteLine($" Số mẫu : {history.Count}");
Console.WriteLine($" Min : {history.Min():F1} °C");
Console.WriteLine($" Max : {history.Max():F1} °C");
Console.WriteLine($" TB : {history.Average():F2} °C");
// Thêm mẫu mới + xóa cũ nhất (sliding window)
history.Add(26.0);
history.RemoveAt(0);
Console.WriteLine($"nSau cập nhật: TB = {history.Average():F2} °C");
// Lọc mẫu vượt ngưỡng 28°C
var high = history.Where(t => t > 28.0).ToList();
Console.WriteLine($"Mẫu >28°C : [{string.Join(", ", high)}]");
}
}
Số mẫu : 10
Min : 24.1 °C
Max : 30.5 °C
TB : 26.96 °C
Sau cập nhật: TB = 27.16 °C
Mẫu >28°C : [28.2, 30.5, 29.1]
Dictionary – Bản đồ cổng IO và trạng thái
Cơ bản
▾
Dùng Dictionary<string, bool> để quản lý trạng thái các cổng IO của thiết bị PLC/IoT — thay thế cho mảng bit cứng nhắc.
using System;
using System.Collections.Generic;
class IOMap
{
static void Main()
{
// Tên cổng → trạng thái
var ioState = new Dictionary<string, bool>
{
{ "DI0 – Công tắc hành trình", true },
{ "DI1 – Nút DỪNG khẩn", false },
{ "DO0 – Contactor bơm", true },
{ "DO1 – Đèn báo lỗi", false },
};
Console.WriteLine("Trạng thái cổng I/O:");
foreach (var port in ioState)
{
string icon = port.Value ? "[ON ]" : "[OFF]";
Console.WriteLine($" {icon} {port.Key}");
}
// Cập nhật trạng thái
ioState["DO1 – Đèn báo lỗi"] = true;
Console.WriteLine($"nSau lỗi: Đèn báo = {ioState["DO1 – Đèn báo lỗi"]}");
}
}
[ON ] DI0 – Công tắc hành trình
[OFF] DI1 – Nút DỪNG khẩn
[ON ] DO0 – Contactor bơm
[OFF] DO1 – Đèn báo lỗi
Sau lỗi: Đèn báo = True
Class & Object – Mô hình hóa thiết bị cảm biến
Cơ bản
▾
Xây dựng class Sensor với properties và method — mô hình OOP cơ bản để đại diện cho thiết bị IoT thực tế.
using System;
class Sensor
{
public string Name { get; set; }
public string Unit { get; set; }
public double Value { get; private set; }
public DateTime LastUpdate { get; private set; }
public Sensor(string name, string unit)
{
Name = name;
Unit = unit;
}
public void Update(double newValue)
{
Value = newValue;
LastUpdate = DateTime.Now;
}
public override string ToString() =>
$"[{Name}] {Value:F2} {Unit} @ {LastUpdate:HH:mm:ss}";
}
class Program
{
static void Main()
{
var tempSensor = new Sensor("Nhiệt độ", "°C");
var humSensor = new Sensor("Độ ẩm", "%RH");
var presSensor = new Sensor("Áp suất", "bar");
tempSensor.Update(28.5);
humSensor.Update(63.2);
presSensor.Update(1.013);
Console.WriteLine(tempSensor);
Console.WriteLine(humSensor);
Console.WriteLine(presSensor);
}
}
[Độ ẩm] 63.20 %RH @ 09:15:42
[Áp suất] 1.01 bar @ 09:15:42
Enum & Switch – Máy trạng thái thiết bị
Cơ bản
▾
Dùng enum để định nghĩa trạng thái và switch để xử lý chuyển đổi — nền tảng của Finite State Machine trong IoT/embedded.
using System;
enum DeviceState
{
Idle, Running, Fault, Maintenance
}
class StateMachine
{
static string Describe(DeviceState state) => state switch
{
DeviceState.Idle => "⚪ Chờ – sẵn sàng nhận lệnh",
DeviceState.Running => "🟢 Đang chạy – bình thường",
DeviceState.Fault => "🔴 LỖI – cần can thiệp",
DeviceState.Maintenance => "🔧 Bảo trì – không hoạt động",
_ => "❓ Không xác định"
};
static void Main()
{
DeviceState[] flow = {
DeviceState.Idle,
DeviceState.Running,
DeviceState.Fault,
DeviceState.Maintenance,
DeviceState.Idle
};
Console.WriteLine("--- Chuỗi trạng thái thiết bị ---");
foreach (var s in flow)
Console.WriteLine($" [{(int)s}] {s,-14} → {Describe(s)}");
}
}
[0] Idle → ⚪ Chờ – sẵn sàng nhận lệnh
[1] Running → 🟢 Đang chạy – bình thường
[2] Fault → 🔴 LỖI – cần can thiệp
[3] Maintenance → 🔧 Bảo trì – không hoạt động
[0] Idle → ⚪ Chờ – sẵn sàng nhận lệnh
Kế thừa (Inheritance) – Các loại cảm biến khác nhau
Trung cấp
▾
Dùng kế thừa OOP để tạo các lớp cảm biến chuyên biệt từ lớp cơ sở. Giúp tái sử dụng code và mở rộng dễ dàng khi thêm loại cảm biến mới.
using System;
using System.Collections.Generic;
abstract class SensorBase
{
public string Id { get; init; }
public double Raw { get; protected set; }
public abstract string GetReading();
public virtual void SetRaw(double v) => Raw = v;
}
class TemperatureSensor : SensorBase
{
public override string GetReading() =>
$"🌡 {Id}: {Raw:F1} °C";
}
class HumiditySensor : SensorBase
{
public override string GetReading() =>
$"💧 {Id}: {Raw:F1} %RH";
}
class PressureSensor : SensorBase
{
public override string GetReading() =>
$"🔵 {Id}: {Raw:F3} bar";
}
class Program
{
static void Main()
{
var sensors = new List<SensorBase>
{
new TemperatureSensor { Id = "T-01" },
new HumiditySensor { Id = "H-01" },
new PressureSensor { Id = "P-01" },
};
sensors[0].SetRaw(29.4);
sensors[1].SetRaw(68.5);
sensors[2].SetRaw(1.025);
Console.WriteLine("=== Đọc tất cả cảm biến ===");
foreach (var s in sensors)
Console.WriteLine(s.GetReading());
}
}
🌡 T-01: 29.4 °C
💧 H-01: 68.5 %RH
🔵 P-01: 1.025 bar
Interface – Plugin đa giao thức truyền thông
Trung cấp
▾
Dùng interface để định nghĩa hợp đồng truyền thông, sau đó cài đặt cho MQTT, HTTP, Serial — giống kiến trúc driver trong IoT gateway.
using System;
interface ITransport
{
string Protocol { get; }
void Send(string topic, string payload);
bool IsConnected();
}
class MqttTransport : ITransport
{
public string Protocol => "MQTT";
public bool IsConnected() => true;
public void Send(string topic, string payload) =>
Console.WriteLine($"[MQTT] → {topic}: {payload}");
}
class HttpTransport : ITransport
{
public string Protocol => "HTTP";
public bool IsConnected() => true;
public void Send(string topic, string payload) =>
Console.WriteLine($"[HTTP] POST /{topic} body={payload}");
}
class SerialTransport : ITransport
{
public string Protocol => "Serial";
public bool IsConnected() => false; // Mô phỏng mất kết nối
public void Send(string topic, string payload) =>
Console.WriteLine($"[Serial] TX: {payload}");
}
class Program
{
static void Publish(ITransport transport, string data)
{
if (!transport.IsConnected())
{
Console.WriteLine($"[{transport.Protocol}] ⚠ Mất kết nối!");
return;
}
transport.Send("sensor/temp", data);
}
static void Main()
{
ITransport[] transports = {
new MqttTransport(),
new HttpTransport(),
new SerialTransport()
};
foreach (var t in transports)
Publish(t, "{"temp":28.5}");
}
}
[HTTP] POST /sensor/temp body={“temp”:28.5}
[Serial] ⚠ Mất kết nối!
Exception Handling – Xử lý lỗi đọc cảm biến
Trung cấp
▾
Dùng try/catch/finally để xử lý lỗi khi đọc thiết bị — không để exception làm crash ứng dụng IoT đang chạy liên tục.
using System;
class SensorException : Exception
{
public string SensorId { get; }
public SensorException(string id, string msg)
: base(msg) => SensorId = id;
}
class SensorReader
{
static double Read(string id, double simulatedRaw)
{
if (simulatedRaw < 4.0) // Dưới 4mA = đứt dây
throw new SensorException(id, "Đứt dây tín hiệu");
if (simulatedRaw > 20.5) // Trên 20.5mA = ngắn mạch
throw new SensorException(id, "Quá dòng / ngắn mạch");
return (simulatedRaw - 4.0) / 16.0 * 100.0;
}
static void SafeRead(string id, double raw)
{
try
{
double val = Read(id, raw);
Console.WriteLine($"✅ [{id}] = {val:F1} %");
}
catch (SensorException ex)
{
Console.WriteLine($"❌ [{ex.SensorId}] LỖI: {ex.Message}");
}
finally
{
Console.WriteLine($" (raw={raw} mA)n");
}
}
static void Main()
{
SafeRead("LV-01", 12.0); // OK
SafeRead("PT-02", 2.5); // Đứt dây
SafeRead("FT-03", 21.0); // Quá dòng
}
}
(raw=12 mA)
❌ [PT-02] LỖI: Đứt dây tín hiệu
(raw=2.5 mA)
❌ [FT-03] LỖI: Quá dòng / ngắn mạch
(raw=21 mA)
LINQ – Truy vấn và lọc dữ liệu cảm biến
Trung cấp
▾
Dùng LINQ để lọc, sắp xếp và nhóm dữ liệu thiết bị — thay thế nhiều vòng lặp phức tạp bằng cú pháp gọn gàng, dễ đọc.
using System;
using System.Collections.Generic;
using System.Linq;
record DeviceData(string Id, string Zone, double Temp, bool Online);
class Program
{
static void Main()
{
var devices = new List<DeviceData>
{
new("S01", "Kho A", 26.5, true),
new("S02", "Kho A", 32.0, true),
new("S03", "Kho B", 19.8, false),
new("S04", "Kho B", 28.4, true),
new("S05", "Kho C", 35.2, true),
};
// 1. Thiết bị online nhiệt độ > 28°C
var hotOnline = devices
.Where(d => d.Online && d.Temp > 28.0)
.OrderByDescending(d => d.Temp);
Console.WriteLine("🔴 Online & Temp > 28°C:");
foreach (var d in hotOnline)
Console.WriteLine($" {d.Id} [{d.Zone}] = {d.Temp:F1}°C");
// 2. Nhiệt độ trung bình theo khu
var byZone = devices
.GroupBy(d => d.Zone)
.Select(g => new { Zone = g.Key, Avg = g.Average(d => d.Temp) });
Console.WriteLine("n📊 TB nhiệt độ theo khu:");
foreach (var z in byZone)
Console.WriteLine($" {z.Zone}: {z.Avg:F1}°C");
// 3. Thiết bị offline
int offCount = devices.Count(d => !d.Online);
Console.WriteLine($"n⚫ Offline: {offCount} thiết bị");
}
}
S05 [Kho C] = 35.2°C
S02 [Kho A] = 32.0°C
S04 [Kho B] = 28.4°C
📊 TB nhiệt độ theo khu:
Kho A: 29.3°C
Kho B: 24.1°C
Kho C: 35.2°C
⚫ Offline: 1 thiết bị
File I/O – Ghi và đọc log CSV
Trung cấp
▾
Ghi dữ liệu cảm biến ra file CSV và đọc lại để phân tích — thao tác cốt lõi của mọi hệ thống data logging trong IoT và SCADA.
using System;
using System.IO;
using System.Collections.Generic;
class CsvLogger
{
const string FILE = "sensor_log.csv";
record SensorRow(string Timestamp, string Device,
double Temp, double Humidity);
static void WriteLog(List<SensorRow> rows)
{
using var sw = new StreamWriter(FILE, append: false);
sw.WriteLine("Timestamp,Device,Temp,Humidity"); // Header
foreach (var r in rows)
sw.WriteLine($"{r.Timestamp},{r.Device},{r.Temp:F2},{r.Humidity:F1}");
Console.WriteLine($"✅ Đã ghi {rows.Count} dòng → {FILE}");
}
static List<SensorRow> ReadLog()
{
var rows = new List<SensorRow>();
var lines = File.ReadAllLines(FILE);
foreach (var line in lines[1..]) // Bỏ header
{
var p = line.Split(',');
rows.Add(new SensorRow(p[0], p[1],
double.Parse(p[2]), double.Parse(p[3])));
}
return rows;
}
static void Main()
{
var data = new List<SensorRow>
{
new("2024-01-15 08:00", "S01", 25.4, 62.3),
new("2024-01-15 08:05", "S01", 26.1, 63.0),
new("2024-01-15 08:10", "S02", 28.7, 58.1),
};
WriteLog(data);
var loaded = ReadLog();
Console.WriteLine("n📂 Dữ liệu đọc lại:");
foreach (var r in loaded)
Console.WriteLine($" {r.Timestamp} | {r.Device} | {r.Temp}°C | {r.Humidity}%");
}
}
📂 Dữ liệu đọc lại:
2024-01-15 08:00 | S01 | 25.4°C | 62.3%
2024-01-15 08:05 | S01 | 26.1°C | 63%
2024-01-15 08:10 | S02 | 28.7°C | 58.1%
Async/Await – Đọc nhiều cảm biến đồng thời
Trung cấp
▾
Dùng async/await để đọc nhiều cảm biến song song thay vì tuần tự — giảm thời gian chờ đáng kể trong IoT gateway thu thập đa nguồn.
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
class AsyncSensorHub
{
// Mô phỏng đọc cảm biến với độ trễ mạng/IO
static async Task<double> ReadSensorAsync(string id, int delayMs)
{
Console.WriteLine($" ⏳ Đang đọc {id}...");
await Task.Delay(delayMs); // Giả lập I/O delay
var rng = new Random();
double val = 20 + rng.NextDouble() * 15;
Console.WriteLine($" ✅ {id} xong: {val:F1}");
return val;
}
static async Task Main()
{
Console.WriteLine("=== Đọc tuần tự ===");
var sw = System.Diagnostics.Stopwatch.StartNew();
double t1 = await ReadSensorAsync("Temp-1", 300);
double t2 = await ReadSensorAsync("Humid-1", 400);
double t3 = await ReadSensorAsync("Press-1", 200);
Console.WriteLine($"Tuần tự: {sw.ElapsedMilliseconds}msn");
Console.WriteLine("=== Đọc song song ===");
sw.Restart();
var results = await Task.WhenAll(
ReadSensorAsync("Temp-2", 300),
ReadSensorAsync("Humid-2", 400),
ReadSensorAsync("Press-2", 200)
);
Console.WriteLine($"Song song: {sw.ElapsedMilliseconds}ms");
Console.WriteLine($"Kết quả: T={results[0]:F1} H={results[1]:F1} P={results[2]:F1}");
}
}
⏳ Đang đọc Temp-1…
✅ Temp-1 xong: 27.3
⏳ Đang đọc Humid-1…
✅ Humid-1 xong: 31.5
⏳ Đang đọc Press-1…
✅ Press-1 xong: 24.8
Tuần tự: ~900ms
=== Đọc song song ===
⏳ Đang đọc Temp-2…
⏳ Đang đọc Humid-2…
⏳ Đang đọc Press-2…
✅ Press-2 xong: 26.1
✅ Temp-2 xong: 29.4
✅ Humid-2 xong: 22.7
Song song: ~400ms
JSON Serialization – Đóng gói dữ liệu MQTT payload
Trung cấp
▾
Dùng System.Text.Json để serialize/deserialize object thành JSON — định dạng trao đổi dữ liệu chuẩn trong MQTT, REST API, IoT cloud.
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
class SensorPayload
{
[JsonPropertyName("device_id")]
public string DeviceId { get; set; } = "";
[JsonPropertyName("timestamp")]
public long Timestamp { get; set; }
[JsonPropertyName("temp")]
public double Temp { get; set; }
[JsonPropertyName("humidity")]
public double Humidity { get; set; }
[JsonPropertyName("status")]
public string Status { get; set; } = "ok";
}
class Program
{
static void Main()
{
var payload = new SensorPayload
{
DeviceId = "ESP32-A3F2",
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
Temp = 27.8,
Humidity = 64.5,
Status = "ok"
};
var options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(payload, options);
Console.WriteLine("📤 Publish MQTT payload:");
Console.WriteLine(json);
// Giả lập nhận lại (subscribe)
var received = JsonSerializer.Deserialize<SensorPayload>(json);
Console.WriteLine($"n📥 Nhận: Device={received!.DeviceId}, T={received.Temp}°C");
}
}
{
“device_id”: “ESP32-A3F2”,
“timestamp”: 1705302000,
“temp”: 27.8,
“humidity”: 64.5,
“status”: “ok”
}
📥 Nhận: Device=ESP32-A3F2, T=27.8°C
Delegate & Event – Hệ thống cảnh báo sự kiện
Nâng cao
▾
Dùng delegate và event để xây dựng hệ thống pub/sub cảnh báo — khi cảm biến vượt ngưỡng, tự động thông báo tới nhiều subscriber (log, email, buzzer…).
using System;
class AlarmEventArgs : EventArgs
{
public string SensorId { get; init; } = "";
public double Value { get; init; }
public double Threshold{ get; init; }
}
class AlarmSensor
{
public string Id { get; init; } = "";
public double Threshold { get; set; }
// Khai báo event
public event EventHandler<AlarmEventArgs>? AlarmTriggered;
public void SetValue(double v)
{
Console.Write($"[{Id}] = {v:F1} ");
if (v > Threshold)
{
Console.WriteLine("← ⚠ ALARM!");
AlarmTriggered?.Invoke(this, new AlarmEventArgs
{
SensorId = Id,
Value = v,
Threshold = Threshold
});
}
else Console.WriteLine("← OK");
}
}
class Program
{
static void Main()
{
var sensor = new AlarmSensor { Id = "TEMP-01", Threshold = 30.0 };
// Đăng ký nhiều handler cho cùng một event
sensor.AlarmTriggered += (s, e) =>
Console.WriteLine($" 📧 Email: [{e.SensorId}] = {e.Value:F1} > {e.Threshold}");
sensor.AlarmTriggered += (s, e) =>
Console.WriteLine($" 🔔 Buzzer: BEEP BEEP!");
sensor.AlarmTriggered += (s, e) =>
Console.WriteLine($" 💾 Log: Alarm at {DateTime.Now:HH:mm:ss}");
Console.WriteLine("--- Mô phỏng đọc cảm biến ---");
foreach (double v in new[] { 25.0, 29.5, 32.1, 28.0, 35.6 })
sensor.SetValue(v);
}
}
[TEMP-01] = 25.0 ← OK
[TEMP-01] = 29.5 ← OK
[TEMP-01] = 32.1 ← ⚠ ALARM!
📧 Email: [TEMP-01] = 32.1 > 30
🔔 Buzzer: BEEP BEEP!
💾 Log: Alarm at 09:15:42
[TEMP-01] = 28.0 ← OK
[TEMP-01] = 35.6 ← ⚠ ALARM!
📧 Email: [TEMP-01] = 35.6 > 30
🔔 Buzzer: BEEP BEEP!
💾 Log: Alarm at 09:15:42
Generic Class – Bộ đệm vòng tổng quát
Nâng cao
▾
Cài đặt CircularBuffer<T> dạng generic — dùng được cho bất kỳ kiểu dữ liệu (double, string, struct). Ứng dụng làm sliding window, FIFO queue.
using System;
using System.Collections.Generic;
using System.Linq;
class CircularBuffer<T>
{
private readonly T[] _buf;
private int _head, _count;
public int Capacity => _buf.Length;
public int Count => _count;
public bool IsFull => _count == Capacity;
public CircularBuffer(int capacity) => _buf = new T[capacity];
public void Push(T item)
{
_buf[(_head + _count) % Capacity] = item;
if (IsFull) _head = (_head + 1) % Capacity; // Ghi đè cũ
else _count++;
}
public IEnumerable<T> Items()
{
for (int i = 0; i < _count; i++)
yield return _buf[(_head + i) % Capacity];
}
}
class Program
{
static void Main()
{
var tempBuf = new CircularBuffer<double>(5);
Console.WriteLine("Nạp 8 mẫu vào buffer size=5:");
foreach (double v in new[] {20.1,21.5,23.0,22.4,25.1,26.8,24.3,27.0})
{
tempBuf.Push(v);
Console.Write($"{v} ");
}
Console.WriteLine($"nnBuffer hiện tại (5 mẫu gần nhất):");
Console.WriteLine(string.Join(", ", tempBuf.Items()));
double avg = tempBuf.Items().Average();
Console.WriteLine($"Trung bình: {avg:F2} °C");
// Generic: dùng cho string cũng được!
var logBuf = new CircularBuffer<string>(3);
logBuf.Push("INFO: Start");
logBuf.Push("WARN: High temp");
logBuf.Push("ERROR: Timeout");
logBuf.Push("INFO: Retry OK");
Console.WriteLine("nLog (3 dòng cuối):");
foreach (var l in logBuf.Items())
Console.WriteLine($" {l}");
}
}
20.1 21.5 23.0 22.4 25.1 26.8 24.3 27.0
Buffer hiện tại (5 mẫu gần nhất):
25.1, 26.8, 24.3, 27.0
Trung bình: 25.47 °C
Log (3 dòng cuối):
WARN: High temp
ERROR: Timeout
INFO: Retry OK
Dependency Injection – Thiết kế IoT service linh hoạt
Nâng cao
▾
Áp dụng DI pattern để tách biệt logic nghiệp vụ khỏi infrastructure — giúp dễ dàng thay đổi storage, transport mà không cần sửa core service.
using System;
using System.Collections.Generic;
// Interfaces (hợp đồng)
interface IDataStore
{
void Save(string key, double val);
double Get(string key);
}
interface INotifier
{
void Notify(string message);
}
// Triển khai cụ thể
class InMemoryStore : IDataStore
{
readonly Dictionary<string, double> _db = new();
public void Save(string k, double v) { _db[k] = v; Console.WriteLine($" [InMemory] Lưu {k}={v}"); }
public double Get(string k) => _db.GetValueOrDefault(k);
}
class ConsoleNotifier : INotifier
{
public void Notify(string msg) =>
Console.WriteLine($" [Console] 🔔 {msg}");
}
class MqttNotifier : INotifier
{
public void Notify(string msg) =>
Console.WriteLine($" [MQTT] 📡 Publish: {msg}");
}
// Service sử dụng DI – không phụ thuộc cài đặt cụ thể
class SensorService
{
readonly IDataStore _store;
readonly INotifier _notifier;
const double LIMIT = 30.0;
public SensorService(IDataStore store, INotifier notifier)
{
_store = store;
_notifier = notifier;
}
public void Process(string id, double val)
{
_store.Save(id, val);
if (val > LIMIT)
_notifier.Notify($"ALARM: {id}={val:F1} > {LIMIT}");
}
}
class Program
{
static void Main()
{
Console.WriteLine("--- Dùng Console Notifier ---");
var svc1 = new SensorService(new InMemoryStore(), new ConsoleNotifier());
svc1.Process("T-01", 25.0);
svc1.Process("T-01", 33.5);
Console.WriteLine("n--- Đổi sang MQTT Notifier ---");
var svc2 = new SensorService(new InMemoryStore(), new MqttNotifier());
svc2.Process("T-02", 31.2);
}
}
[InMemory] Lưu T-01=25
[InMemory] Lưu T-01=33.5
[Console] 🔔 ALARM: T-01=33.5 > 30
— Đổi sang MQTT Notifier —
[InMemory] Lưu T-02=31.2
[MQTT] 📡 Publish: ALARM: T-02=31.2 > 30
TCP Socket Client – Kết nối tới IoT Server
Nâng cao
▾
Xây dựng TCP client gửi dữ liệu cảm biến và TCP server nhận — mô phỏng giao tiếp giữa edge device và IoT gateway qua mạng LAN.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
class IoTServer
{
public static async Task StartAsync(int port)
{
var listener = new TcpListener(IPAddress.Loopback, port);
listener.Start();
Console.WriteLine($"[Server] Lắng nghe cổng {port}...");
using var client = await listener.AcceptTcpClientAsync();
Console.WriteLine("[Server] Client kết nối!");
using var stream = client.GetStream();
byte[] buf = new byte[256];
while (true)
{
int n = await stream.ReadAsync(buf);
if (n == 0) break;
string msg = Encoding.UTF8.GetString(buf, 0, n);
Console.WriteLine($"[Server] Nhận: {msg}");
// Gửi ACK
byte[] ack = Encoding.UTF8.GetBytes("ACK");
await stream.WriteAsync(ack);
}
listener.Stop();
}
}
class IoTClient
{
public static async Task RunAsync(int port)
{
using var client = new TcpClient();
await client.ConnectAsync(IPAddress.Loopback, port);
using var stream = client.GetStream();
byte[] buf = new byte[64];
string[] payloads = {
"{"t":25.1,"h":62}",
"{"t":27.8,"h":65}",
"{"t":31.2,"h":70}",
};
foreach (var p in payloads)
{
byte[] data = Encoding.UTF8.GetBytes(p);
await stream.WriteAsync(data);
Console.WriteLine($"[Client] Gửi: {p}");
int n = await stream.ReadAsync(buf);
Console.WriteLine($"[Client] ACK: {Encoding.UTF8.GetString(buf, 0, n)}");
await Task.Delay(200);
}
}
}
class Program
{
static async Task Main()
{
const int PORT = 9000;
var serverTask = IoTServer.StartAsync(PORT);
await Task.Delay(100); // Cho server khởi động
await Task.WhenAll(serverTask, IoTClient.RunAsync(PORT));
}
}
[Server] Client kết nối!
[Client] Gửi: {“t”:25.1,”h”:62}
[Server] Nhận: {“t”:25.1,”h”:62}
[Client] ACK: ACK
[Client] Gửi: {“t”:27.8,”h”:65}
[Server] Nhận: {“t”:27.8,”h”:65}
[Client] ACK: ACK
[Client] Gửi: {“t”:31.2,”h”:70}
[Server] Nhận: {“t”:31.2,”h”:70}
[Client] ACK: ACK
Background Service – Thu thập định kỳ kiểu daemon
Nâng cao
▾
Cài đặt service chạy nền liên tục (daemon pattern) dùng CancellationToken — chuẩn mực cho IoT edge service cần chạy 24/7 với graceful shutdown.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
class SensorDaemon
{
private readonly int _intervalMs;
private readonly string _sensorId;
public List<double> Readings { get; } = new();
public SensorDaemon(string id, int intervalMs)
{
_sensorId = id;
_intervalMs = intervalMs;
}
public async Task RunAsync(CancellationToken ct)
{
Console.WriteLine($"[{_sensorId}] Daemon khởi động (interval={_intervalMs}ms)");
var rng = new Random();
int tick = 0;
try
{
while (!ct.IsCancellationRequested)
{
double val = 20 + rng.NextDouble() * 15;
Readings.Add(val);
Console.WriteLine($" [{_sensorId}] Tick {++tick:D3}: {val:F2} °C");
await Task.Delay(_intervalMs, ct);
}
}
catch (TaskCanceledException) { /* Expected khi cancel */ }
Console.WriteLine($"[{_sensorId}] Daemon dừng. Đã đọc {Readings.Count} mẫu.");
}
}
class Program
{
static async Task Main()
{
using var cts = new CancellationTokenSource();
var daemon = new SensorDaemon("TEMP-01", 300);
var task = daemon.RunAsync(cts.Token);
// Chạy 1.5 giây rồi dừng (mô phỏng Ctrl+C)
await Task.Delay(1500);
Console.WriteLine("n[Main] Gửi lệnh dừng...");
cts.Cancel();
await task;
Console.WriteLine($"[Main] TB = {daemon.Readings.Average():F2} °C");
}
}
[TEMP-01] Tick 001: 27.43 °C
[TEMP-01] Tick 002: 31.18 °C
[TEMP-01] Tick 003: 24.75 °C
[TEMP-01] Tick 004: 29.62 °C
[TEMP-01] Tick 005: 26.31 °C
[Main] Gửi lệnh dừng…
[TEMP-01] Daemon dừng. Đã đọc 5 mẫu.
[Main] TB = 27.86 °C
Mini IoT Hub – Tổng hợp toàn bộ
Nâng cao
▾
Kết hợp class, interface, generic, event, async, LINQ và JSON để xây dựng mini IoT Hub thu thập dữ liệu từ nhiều thiết bị, phát cảnh báo và xuất báo cáo.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
// ── Model ──
record Reading(string DeviceId, string Type, double Value, DateTime At);
// ── Alert rule ──
record AlertRule(string Type, double MaxValue);
// ── IoT Hub ──
class IoTHub
{
private readonly List<Reading> _readings = new();
private readonly List<AlertRule> _rules = new();
public event Action<Reading>? OnAlert;
public void AddRule(AlertRule rule) => _rules.Add(rule);
public void Ingest(Reading r)
{
_readings.Add(r);
var rule = _rules.FirstOrDefault(x => x.Type == r.Type);
if (rule != null && r.Value > rule.MaxValue)
OnAlert?.Invoke(r);
}
public void PrintReport()
{
Console.WriteLine("n╔══════════ IoT Hub Report ══════════╗");
var byDevice = _readings.GroupBy(r => r.DeviceId);
foreach (var grp in byDevice)
{
Console.WriteLine($"║ Device: {grp.Key}");
foreach (var typeGrp in grp.GroupBy(r => r.Type))
{
var vals = typeGrp.Select(r => r.Value).ToList();
Console.WriteLine($"║ {typeGrp.Key,-10}: min={vals.Min():F1} max={vals.Max():F1} avg={vals.Average():F1}");
}
}
Console.WriteLine($"║ Tổng readings: {_readings.Count}");
Console.WriteLine("╚════════════════════════════════════╝");
}
}
class Program
{
static void Main()
{
var hub = new IoTHub();
hub.AddRule(new AlertRule("temp", 30.0));
hub.AddRule(new AlertRule("humidity", 80.0));
hub.OnAlert += r =>
Console.WriteLine($" ⚠ ALERT [{r.DeviceId}] {r.Type}={r.Value:F1} @ {r.At:HH:mm:ss}");
// Mô phỏng nhận dữ liệu
var data = new (string dev, string type, double val)[]
{
("ESP32-01", "temp", 26.5),
("ESP32-01", "humidity", 65.0),
("ESP32-01", "temp", 32.1), // Alert!
("ESP32-02", "temp", 24.8),
("ESP32-02", "humidity", 83.5), // Alert!
("ESP32-02", "temp", 28.3),
};
Console.WriteLine("Nhận dữ liệu từ thiết bị:");
foreach (var (dev, type, val) in data)
{
Console.Write($" ← {dev} {type}={val:F1} ");
hub.Ingest(new Reading(dev, type, val, DateTime.Now));
Console.WriteLine();
}
hub.PrintReport();
}
}
← ESP32-01 temp=26.5
← ESP32-01 humidity=65.0
← ESP32-01 temp=32.1
⚠ ALERT [ESP32-01] temp=32.1 @ 09:15:43
← ESP32-02 temp=24.8
← ESP32-02 humidity=83.5
⚠ ALERT [ESP32-02] humidity=83.5 @ 09:15:43
← ESP32-02 temp=28.3
╔══════════ IoT Hub Report ══════════╗
║ Device: ESP32-01
║ temp : min=26.5 max=32.1 avg=29.3
║ humidity : min=65.0 max=65.0 avg=65.0
║ Device: ESP32-02
║ temp : min=24.8 max=28.3 avg=26.6
║ humidity : min=83.5 max=83.5 avg=83.5
║ Tổng readings: 6
╚════════════════════════════════════╝
[/ux_html]
