Ôn tập C# cho IoT

[ux_html]






C# cho Kỹ Sư IoT & Tự Động Hóa


// C# · IoT & Automation · Ôn tập thực hành

Học C#
Cho Dân IoT

$ 20 bài tập từ cơ bản → thực chiến

20
Bài tập
3
Cấp độ
Lọc:



#01
Biến, kiểu dữ liệu và nhập/xuất Console
Cơ bản
// Mô tả

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.

// Code
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");
    }
}
=== Thông tin thiết bị IoT ===
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

biếnkiểu dữ liệuConsolestring interpolation

#02
Câu lệnh điều kiện – Cảnh báo ngưỡng cảm biến
Cơ bản
// Mô tả

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.

// Code
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}");
        }
    }
}
25.0°C → 🟢 BÌNH THƯỜNG
65.3°C → 🟡 CẢNH BÁO
85.7°C → 🔴 NGUY HIỂM
-5.0°C → ❌ LỖI CẢM BIẾN
if/elsealarmcảm biến nhiệt độ

#03
Vòng lặp – Mô phỏng chu kỳ đọc cảm biến
Cơ bản
// Mô tả

Dùng forwhile để 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.

// Code
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.");
    }
}
Chu kỳ đọc cảm biến (mỗi 500ms):
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.

for loopRandompollingcảm biến

#04
Mảng và List – Lưu trữ lịch sử đo lường
Cơ bản
// Mô tả

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.

// Code
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)}]");
    }
}
📊 Báo cáo nhiệt độ hôm nay:
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]

ListLINQdata loggingstatistics

#05
Dictionary – Bản đồ cổng IO và trạng thái
Cơ bản
// Mô tả

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.

// Code
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"]}");
    }
}
Trạng thái cổng I/O:
[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

DictionaryI/O mapPLCtrạng thái

#06
Class & Object – Mô hình hóa thiết bị cảm biến
Cơ bản
// Mô tả

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ế.

// Code
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);
    }
}
[Nhiệt độ] 28.50 °C @ 09:15:42
[Độ ẩm] 63.20 %RH @ 09:15:42
[Áp suất] 1.01 bar @ 09:15:42
classobjectpropertiesOOP

#07
Enum & Switch – Máy trạng thái thiết bị
Cơ bản
// Mô tả

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.

// Code
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)}");
    }
}
— Chuỗi trạng thái thiết bị —
[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
enumswitch expressionFSM

#08
Kế thừa (Inheritance) – Các loại cảm biến khác nhau
Trung cấp
// Mô tả

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.

// Code
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());
    }
}
=== Đọc tất cả cảm biến ===
🌡 T-01: 29.4 °C
💧 H-01: 68.5 %RH
🔵 P-01: 1.025 bar
inheritanceabstractpolymorphismOOP

#09
Interface – Plugin đa giao thức truyền thông
Trung cấp
// Mô tả

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.

// Code
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}");
    }
}
[MQTT] → sensor/temp: {“temp”:28.5}
[HTTP] POST /sensor/temp body={“temp”:28.5}
[Serial] ⚠ Mất kết nối!
interfaceMQTTHTTPđa giao thức

#10
Exception Handling – Xử lý lỗi đọc cảm biến
Trung cấp
// Mô tả

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.

// Code
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
    }
}
✅ [LV-01] = 50.0 %
(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)

exceptiontry/catchcustom exception4-20mA

#11
LINQ – Truy vấn và lọc dữ liệu cảm biến
Trung cấp
// Mô tả

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.

// Code
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ị");
    }
}
🔴 Online & Temp > 28°C:
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ị

LINQWhereGroupByrecord

#12
File I/O – Ghi và đọc log CSV
Trung cấp
// Mô tả

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.

// Code
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}%");
    }
}
✅ Đã ghi 3 dòng → sensor_log.csv

📂 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%

File I/OCSVStreamWriterdata logging

#13
Async/Await – Đọc nhiều cảm biến đồng thời
Trung cấp
// Mô tả

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.

// Code
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}");
    }
}
=== Đọc tuần tự ===
⏳ Đ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

💡 Task.WhenAll chạy tất cả task đồng thời và chờ cái chậm nhất — từ 900ms xuống còn ~400ms!
async/awaitTask.WhenAllconcurrencyIoT gateway

#14
JSON Serialization – Đóng gói dữ liệu MQTT payload
Trung cấp
// Mô tả

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.

// Code
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");
    }
}
📤 Publish MQTT payload:
{
“device_id”: “ESP32-A3F2”,
“timestamp”: 1705302000,
“temp”: 27.8,
“humidity”: 64.5,
“status”: “ok”
}

📥 Nhận: Device=ESP32-A3F2, T=27.8°C

JSONSystem.Text.JsonMQTTserialize

#15
Delegate & Event – Hệ thống cảnh báo sự kiện
Nâng cao
// Mô tả

Dùng delegateevent để 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…).

// Code
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);
    }
}
— Mô phỏng đọc cảm biến —
[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
delegateeventpub/subalarm system

#16
Generic Class – Bộ đệm vòng tổng quát
Nâng cao
// Mô tả

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.

// Code
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}");
    }
}
Nạp 8 mẫu vào buffer size=5:
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

genericscircular bufferyield returnFIFO

#17
Dependency Injection – Thiết kế IoT service linh hoạt
Nâng cao
// Mô tả

Á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.

// Code
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);
    }
}
— Dùng Console Notifier —
[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

DIdependency injectioninterfaceclean architecture

#18
TCP Socket Client – Kết nối tới IoT Server
Nâng cao
// Mô tả

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.

// Code
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] Lắng nghe cổng 9000…
[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
TCP SocketasyncnetworkingIoT gateway

#19
Background Service – Thu thập định kỳ kiểu daemon
Nâng cao
// Mô tả

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.

// Code
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] Daemon khởi động (interval=300ms)
[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

CancellationTokenbackground servicedaemongraceful shutdown

#20
Mini IoT Hub – Tổng hợp toàn bộ
Nâng cao
// Mô tả

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.

// Code
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();
    }
}
Nhận dữ liệu từ thiết bị:
← 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
╚════════════════════════════════════╝

tổng hợpeventLINQrecordIoT Hub

C# for IoT & Automation · 20 Bài Tập · 2024




[/ux_html]

[contact-form-7 id=”f245613″ title=”Newsletter”]