ক্যাপস্টোন — একটি সম্পূর্ণ এমবেডেড+IoT সিস্টেম সিমুলেট করা
এই পাঠে যা শিখবেন
- একটি বাস্তব IoT ডিভাইসের সেন্সিং, অ্যাকচুয়েশন, লোকাল প্রোটোকল, শিডিউলিং, কানেক্টিভিটি ও পাওয়ার — এই স্তরগুলো কীভাবে একসাথে কাজ করে
- কীভাবে একটি ধাপের গণনা করা ফলাফল পরের ধাপের ইনপুট হয়ে ওঠে (সেন্সর রিডিং → সিদ্ধান্ত → পাম্প সময় → পাওয়ার হিসাব)
- এই কোর্সে শেখা প্রতিটি বড় প্যাটার্ন (ADC, ক্যালিব্রেশন, PWM, I2C, RTOS শিডিউলার, MQTT ব্রোকার, ব্যাটারি-লাইফ গণনা) একটিমাত্র প্রজেক্টে কীভাবে মিলিত হয়
০ · সিস্টেম ওভারভিউ — স্মার্ট প্ল্যান্ট-ওয়াটারিং কন্ট্রোলার
ডিভাইসটি একটি টবের/বেডের মাটির আর্দ্রতা পর্যবেক্ষণ করে, প্রয়োজনে একটি পানির পাম্প চালায়, এবং তার রিডিং ও কার্যকলাপ একটি MQTT ব্রোকারের মাধ্যমে একটি ড্যাশবোর্ডে পাঠায় — L55-এর কেস স্টাডির ঠিক একই ধরনের ডিভাইস, কিন্তু এখানে প্রতিটি স্তর সত্যিকারের কোডে সিমুলেট করা হবে।
১ · সেন্সর রিড — ADC কোয়ান্টাইজেশন + ক্যালিব্রেশন
প্রথম ধাপ মডিউল ৫/L20-এর ADC কোয়ান্টাইজেশন প্যাটার্ন ও মডিউল ৯/L37-এর দুই-বিন্দু ক্যালিব্রেশন প্যাটার্ন একত্র করে — সেন্সরের raw অ্যানালগ ভোল্টেজ প্রথমে ADC-তে কোয়ান্টাইজ হয় ($step = V_{ref}/2^{bits}$), তারপর শুকনো/ভেজা ক্যালিব্রেশন পয়েন্ট দিয়ে বানানো একটি সরলরেখা দিয়ে raw কোডকে "আর্দ্রতা %" এককে রূপান্তর করা হয়।
$$step = \frac{V_{ref}}{2^{bits}} \qquad\qquad moisture\% = m \cdot code + b$$import random
# ---- ADC কোয়ান্টাইজেশন (M5/L20-এর প্যাটার্ন) ----
ADC_BITS = 12
VREF = 3.3
adc_step = VREF / (2 ** ADC_BITS)
def voltage_to_code(v):
code = round(v / adc_step)
return max(0, min(code, 2 ** ADC_BITS - 1))
# ---- দুই-বিন্দু ক্যালিব্রেশন (M9/L37-এর প্যাটার্ন) ----
# শুকনো বাতাসে (0% আর্দ্রতা) ও পানিতে ডুবিয়ে (100% আর্দ্রতা) মাপা রেফারেন্স ভোল্টেজ
DRY_VOLTAGE = 2.80 # illustrative
WET_VOLTAGE = 1.20 # illustrative
dry_code = voltage_to_code(DRY_VOLTAGE)
wet_code = voltage_to_code(WET_VOLTAGE)
# moisture_pct = m * code + b -- (dry_code, 0%) ও (wet_code, 100%) বিন্দু দুটি দিয়ে সরলরেখা
calib_m = (100 - 0) / (wet_code - dry_code)
calib_b = 0 - calib_m * dry_code
def code_to_moisture_pct(code):
return calib_m * code + calib_b
# ---- একটি "প্রকৃত কিন্তু বাস্তবে অজানা" মাটির আর্দ্রতা থেকে নয়েজি সেন্সর রিডিং সিমুলেট ----
true_moisture_pct = 38.0
true_voltage = DRY_VOLTAGE - (true_moisture_pct / 100) * (DRY_VOLTAGE - WET_VOLTAGE)
rng = random.Random(42)
noisy_voltage = rng.gauss(true_voltage, 0.03) # সেন্সর + ADC নয়েজ, ফিক্সড সিড
raw_code = voltage_to_code(noisy_voltage)
measured_moisture_pct = code_to_moisture_pct(raw_code)
print(f"ADC স্টেপ সাইজ: {adc_step * 1000:.3f} mV ({ADC_BITS}-বিট, Vref={VREF}V)")
print(f"ক্যালিব্রেশন পয়েন্ট: শুকনো={dry_code} কোড (0%), ভেজা={wet_code} কোড (100%)")
print(f"নয়েজি সেন্সর ভোল্টেজ: {noisy_voltage:.3f}V -> raw ADC কোড: {raw_code}")
print(f"ক্যালিব্রেটেড আর্দ্রতা রিডিং: {measured_moisture_pct:.1f}%")
measured_moisture_pct) পায়। এই অনুমানই এখন থেকে বাকি সবগুলো ধাপের ভিত্তি।
২ · সিদ্ধান্ত + অ্যাকচুয়েটর — থ্রেশহোল্ড কন্ট্রোল ও PWM পাম্প
দ্বিতীয় ধাপে ধাপ ১-এর measured_moisture_pct ব্যবহার করে একটি সাধারণ থ্রেশহোল্ড-ভিত্তিক
কন্ট্রোল লজিক সিদ্ধান্ত নেয় পাম্প চালাতে হবে কিনা, এবং মডিউল ৫/L19-এর PWM প্যাটার্ন অনুযায়ী কম্পেয়ার/পিরিয়ড
রেজিস্টার থেকে ডিউটি সাইকেল গণনা করে পাম্পের প্রকৃত HIGH/LOW ওয়েভফর্ম তৈরি করে যাচাই করা হচ্ছে।
# ---- থ্রেশহোল্ড-ভিত্তিক সিদ্ধান্ত -- ধাপ ১-এর measured_moisture_pct ব্যবহার করে ----
MOISTURE_THRESHOLD_PCT = 40.0
moisture_deficit = max(0.0, MOISTURE_THRESHOLD_PCT - measured_moisture_pct)
pump_should_run = moisture_deficit > 0
SECONDS_PER_PERCENT_DEFICIT = 2.5 # illustrative -- যত বেশি শুকনো, পাম্প তত বেশিক্ষণ চলে
pump_duration_s = round(moisture_deficit * SECONDS_PER_PERCENT_DEFICIT, 1)
# ---- PWM ডিউটি সাইকেল (M5/L19-এর প্যাটার্ন) -- পাম্প ড্রাইভার ৯০% ডিউটিতে চালানো হচ্ছে ----
PUMP_PWM_PERIOD = 100 # রেজোলিউশন: ১০০ ধাপ = ১০০%
PUMP_PWM_DUTY_PCT = 90
pump_pwm_compare = round(PUMP_PWM_PERIOD * PUMP_PWM_DUTY_PCT / 100)
waveform = ['HIGH' if tick < pump_pwm_compare else 'LOW' for tick in range(PUMP_PWM_PERIOD)]
high_ticks = waveform.count('HIGH')
measured_duty_pct = 100 * high_ticks / PUMP_PWM_PERIOD
print(f"পরিমাপকৃত আর্দ্রতা: {measured_moisture_pct:.1f}%, থ্রেশহোল্ড: {MOISTURE_THRESHOLD_PCT}%")
print(f"ঘাটতি (deficit): {moisture_deficit:.2f}% -> পাম্প চালাতে হবে: {pump_should_run}")
print(f"পাম্পের সময়কাল: {pump_duration_s} সেকেন্ড")
print(f"PWM কম্পেয়ার রেজিস্টার: {pump_pwm_compare}/{PUMP_PWM_PERIOD} "
f"-> সিমুলেটেড ওয়েভফর্মে HIGH টিক: {high_ticks}/{PUMP_PWM_PERIOD} "
f"= {measured_duty_pct:.0f}% ডিউটি (প্রত্যাশিত {PUMP_PWM_DUTY_PCT}%-এর সাথে মিলছে)")
৩ · কমিউনিকেশন — I2C-এর মাধ্যমে সেন্সর রিড
অনেক বাস্তব মাটির-আর্দ্রতা ব্রেকআউট বোর্ড আসলে একটি বাহ্যিক I2C ADC চিপের পেছনে বসানো থাকে (MCU-এর নিজস্ব
অ্যানালগ পিনের বদলে) — এই ধাপে মডিউল ৬/L24-এর সম্পূর্ণ start/address/ACK/data/stop সিকোয়েন্স পুনরায়
বাস্তবায়ন করে ঠিক সেই raw_code-টিই (ধাপ ১ থেকে) একটি সিমুলেটেড I2C বাসের মাধ্যমে "রিড" করে
দেখানো হচ্ছে, শুধু একটি বেয়ার ফাংশন-কল নয়।
# ---- I2C start/address/ACK/data/stop সিকোয়েন্স (M6/L24-এর প্যাটার্ন) ----
class I2cBus:
def __init__(self, devices):
self.devices = devices # {address: (name, read_fn)}
def read_register(self, address, register):
log = []
log.append("START")
addr_write_byte = (address << 1) | 0
if address not in self.devices:
log.append(f"ADDRESS 0x{address:02X}+W (0x{addr_write_byte:02X}) -> NACK (ডিভাইস নেই)")
log.append("STOP (ট্রানজ্যাকশন বাতিল)")
return None, log
name, read_fn = self.devices[address]
log.append(f"ADDRESS 0x{address:02X}+W (0x{addr_write_byte:02X}) -> ACK from {name}")
log.append(f"DATA 0x{register:02X} (রেজিস্টার পয়েন্টার) -> ACK")
log.append("REPEATED START")
addr_read_byte = (address << 1) | 1
log.append(f"ADDRESS 0x{address:02X}+R (0x{addr_read_byte:02X}) -> ACK from {name}")
value = read_fn()
msb = (value >> 8) & 0xFF
lsb = value & 0xFF
log.append(f"DATA 0x{msb:02X} (MSB) <- ACK (মাস্টার)")
log.append(f"DATA 0x{lsb:02X} (LSB) <- NACK (মাস্টার, শেষ বাইট)")
log.append("STOP")
return value, log
# একটি সিমুলেটেড I2C ADC ব্রেকআউট (ঠিকানা 0x48-এ) -- ধাপ ১-এর raw_code-ই "রিড" হয়
devices = {0x48: ("soil_moisture_adc", lambda: raw_code)}
bus = I2cBus(devices)
i2c_value, i2c_log = bus.read_register(0x48, 0x00)
print("I2C ট্রানজ্যাকশন (সফল, ডিভাইস আছে):")
for line in i2c_log:
print(" ", line)
print(f"\nI2C-এর মাধ্যমে পড়া মান: {i2c_value} -- ধাপ ১-এর raw_code ({raw_code})-এর সাথে মিলছে: {i2c_value == raw_code}")
# তুলনার জন্য: একটি ঠিকানায় কোনো ডিভাইস নেই -> NACK
_, nack_log = bus.read_register(0x50, 0x00)
print("\nI2C ট্রানজ্যাকশন (ব্যর্থ, ডিভাইস নেই ঠিকানা 0x50-এ):")
for line in nack_log:
print(" ", line)
৪ · RTOS-স্টাইল টাস্ক শিডিউলিং
একটি বাস্তব ডিভাইস একই সাথে সেন্সর পড়া, পাম্প নিয়ন্ত্রণ করা, ও নেটওয়ার্কে পাবলিশ করা — এই তিনটি কাজই পরিচালনা করে, একটির পর একটি বসে থেকে নয়। মডিউল ৭/L29-এর প্রায়োরিটি-ভিত্তিক RTOS শিডিউলার প্যাটার্ন এখানে তিনটি সিমুলেটেড টাস্ক নিয়ে পুনর্নির্মাণ করা হয়েছে — প্রতিটি টাস্কের একটি প্রায়োরিটি ও পিরিয়ড আছে, আর শিডিউলার প্রতি টিকে প্রস্তুত (ready) টাস্কগুলোর মধ্যে সর্বোচ্চ-প্রায়োরিটি টাস্কটিই চালায়।
# ---- ছোট প্রায়োরিটি-ভিত্তিক RTOS শিডিউলার (M7/L29-এর প্যাটার্ন) ----
class Task:
def __init__(self, name, priority, period, work_ticks):
self.name = name
self.priority = priority # ছোট সংখ্যা = উচ্চ প্রায়োরিটি
self.period = period # কত টিক পরপর নতুন কাজ আসে
self.work_ticks = work_ticks # প্রতিবার কতটা কাজ (টিক) লাগে
self.remaining = 0
def release(self, tick):
if tick % self.period == 0:
self.remaining += self.work_ticks
tasks = [
Task("sensor_read", priority=1, period=5, work_ticks=1), # সর্বোচ্চ প্রায়োরিটি
Task("pump_control", priority=2, period=5, work_ticks=1),
Task("network_publish", priority=3, period=10, work_ticks=2), # সবচেয়ে কম প্রায়োরিটি, বেশি কাজ
]
TOTAL_TICKS = 20
schedule_trace = []
for tick in range(TOTAL_TICKS):
for t in tasks:
t.release(tick)
ready_tasks = [t for t in tasks if t.remaining > 0]
if ready_tasks:
ready_tasks.sort(key=lambda t: t.priority)
running = ready_tasks[0]
running.remaining -= 1
schedule_trace.append((tick, running.name))
else:
schedule_trace.append((tick, "idle"))
for tick, name in schedule_trace:
print(f"tick {tick:2d}: {name}")
task_run_counts = {}
for _, name in schedule_trace:
task_run_counts[name] = task_run_counts.get(name, 0) + 1
print(f"\n{TOTAL_TICKS} টিকে মোট রান-কাউন্ট: {task_run_counts}")
sensor_read (প্রায়োরিটি ১) সবসময় প্রস্তুত থাকলে সবার আগে চলে (tick ০, ৫,
১০, ১৫), pump_control (প্রায়োরিটি ২) তার ঠিক পরে, আর network_publish (প্রায়োরিটি
৩, কিন্তু ২ টিক কাজ লাগে) শুধু তখনই চলে যখন উচ্চ-প্রায়োরিটি কোনো টাস্ক প্রস্তুত থাকে না (যেমন tick ২-৩ ও
১২-১৩) — এটিই একটি সত্যিকারের প্রায়োরিটি-ভিত্তিক শিডিউলের আচরণ, FIFO নয়।
৫ · কানেক্টিভিটি — MQTT ব্রোকারে পাবলিশ
পঞ্চম ধাপে মডিউল ১১/L46-এর MqttBroker প্যাটার্ন পুনর্নির্মাণ করে — সত্যিকারের টপিক
ম্যাচিং লজিকসহ (+ এক লেভেল, # বাকি সব লেভেল ম্যাচ করে) — ধাপ ১ ও ধাপ ২-এর
ফলাফল farm/bed1/moisture ও farm/bed1/pump টপিকে পাবলিশ করা হচ্ছে, এবং একটি
সাবস্ক্রাইব করা "dashboard" ক্লায়েন্ট প্রকৃত ওয়াইল্ডকার্ড ম্যাচিং যুক্তির মাধ্যমে সেগুলো গ্রহণ করছে কিনা
যাচাই করা হচ্ছে।
# ---- ইন-মেমরি MQTT ব্রোকার, সত্যিকারের ওয়াইল্ডকার্ড ম্যাচিংসহ (M11/L46-এর প্যাটার্ন) ----
class MqttBroker:
def __init__(self):
self.subscriptions = [] # [(client, topic_filter), ...]
def subscribe(self, client, topic_filter):
self.subscriptions.append((client, topic_filter))
@staticmethod
def topic_matches(topic_filter, topic):
filter_levels = topic_filter.split('/')
topic_levels = topic.split('/')
for i, flevel in enumerate(filter_levels):
if flevel == '#':
return True # '#' বাকি সব লেভেল ম্যাচ করে
if i >= len(topic_levels):
return False
if flevel == '+':
continue # '+' ঠিক এক লেভেল ম্যাচ করে
if flevel != topic_levels[i]:
return False
return len(filter_levels) == len(topic_levels)
def publish(self, topic, payload):
delivered_to = []
for client, topic_filter in self.subscriptions:
if self.topic_matches(topic_filter, topic):
delivered_to.append(client)
return delivered_to
broker = MqttBroker()
broker.subscribe("dashboard_bed1", "farm/bed1/#") # আমাদের ডিভাইসের বেড সাবস্ক্রাইব করছে
broker.subscribe("dashboard_bed2", "farm/bed2/#") # ভিন্ন বেডের ড্যাশবোর্ড -- ম্যাচ করা উচিত নয়
moisture_topic = "farm/bed1/moisture"
pump_topic = "farm/bed1/pump"
moisture_payload = f"{measured_moisture_pct:.1f}"
pump_payload = f"ran={pump_should_run},duration_s={pump_duration_s}"
delivered_moisture = broker.publish(moisture_topic, moisture_payload)
delivered_pump = broker.publish(pump_topic, pump_payload)
mqtt_delivered = ("dashboard_bed1" in delivered_moisture) and ("dashboard_bed1" in delivered_pump)
print(f"PUBLISH {moisture_topic} = '{moisture_payload}' -> ডেলিভার হলো: {delivered_moisture}")
print(f"PUBLISH {pump_topic} = '{pump_payload}' -> ডেলিভার হলো: {delivered_pump}")
print(f"'dashboard_bed2' ম্যাচ করেনি (ভিন্ন বেড) -- প্রমাণ করে ম্যাচিং হার্ডকোড নয়, সত্যিকারের ফিল্টার লজিক")
print(f"\nমোট MQTT ডেলিভারি সফল (dashboard_bed1-এ উভয় বার্তা): {mqtt_delivered}")
৬ · পাওয়ার — সম্মিলিত ব্যাটারি-লাইফ গণনা
শেষ গণনামূলক ধাপ — মডিউল ৮/L34-এর ব্যাটারি-লাইফ প্যাটার্ন প্রয়োগ করে, কিন্তু এবার আগের প্রতিটি ধাপের
প্রকৃত ফলাফল ব্যবহার করে: ধাপ ১-৩-এর সেন্সর+I2C রিড সময়, ধাপ ২-এর pump_duration_s (পাম্প
সত্যিই কতক্ষণ চলেছে), আর ধাপ ৫-এর নেটওয়ার্ক পাবলিশ সময় — এই তিনটি "সক্রিয়" সময় আর বাকি চক্রের ডিপ-স্লিপ
সময় দিয়ে একটি ওয়েটেড-এভারেজ কারেন্ট এবং তা থেকে ব্যাটারি-লাইফ গণনা করা হচ্ছে।
# ---- সম্মিলিত ব্যাটারি-লাইফ গণনা (M8/L34-এর প্যাটার্ন) -- আগের ধাপগুলোর ফলাফল ব্যবহার করে ----
CYCLE_PERIOD_S = 600 # প্রতি ১০ মিনিটে একবার সম্পূর্ণ চক্র (সেন্সর + সিদ্ধান্ত + প্রয়োজনে পাম্প + পাবলিশ)
sensor_active_s = 0.02 # ADC রিড + ধাপ ৩-এর I2C ট্রানজ্যাকশন সময়
sensor_active_mA = 15
pump_current_mA = 200 # পাম্প মোটরের ফুল-পাওয়ার কারেন্ট (illustrative)
pump_duty_fraction = PUMP_PWM_DUTY_PCT / 100 # ধাপ ২-এর PWM ডিউটি সাইকেল
pump_active_s = pump_duration_s if pump_should_run else 0 # ধাপ ২-এর প্রকৃত ফলাফল
pump_avg_current_during_run_mA = pump_current_mA * pump_duty_fraction
publish_active_s = 1.5 # ধাপ ৫-এর WiFi+MQTT পাবলিশ সময়
publish_mA = 120
sleep_mA = 0.01 # বাকি সময় ডিপ-স্লিপে (M8-এর লো-পাওয়ার মোড)
active_time_used_s = sensor_active_s + pump_active_s + publish_active_s
sleep_time_s = CYCLE_PERIOD_S - active_time_used_s
charge_per_cycle_mA_s = (
sensor_active_mA * sensor_active_s
+ pump_avg_current_during_run_mA * pump_active_s
+ publish_mA * publish_active_s
+ sleep_mA * sleep_time_s
)
average_current_mA = charge_per_cycle_mA_s / CYCLE_PERIOD_S
BATTERY_CAPACITY_MAH = 3000 # একটি ছোট 18650-জাতীয় Li-ion প্যাক (illustrative)
battery_life_hours = BATTERY_CAPACITY_MAH / average_current_mA
battery_life_days = battery_life_hours / 24
print(f"চক্র প্রতি সক্রিয় সময়: সেন্সর {sensor_active_s}s + পাম্প {pump_active_s}s + পাবলিশ {publish_active_s}s "
f"= {active_time_used_s:.2f}s (ডিপ-স্লিপ: {sleep_time_s:.2f}s)")
print(f"গড় কারেন্ট ড্র: {average_current_mA:.3f} mA")
print(f"ব্যাটারি ক্যাপাসিটি: {BATTERY_CAPACITY_MAH} mAh")
print(f"আনুমানিক ব্যাটারি-লাইফ: {battery_life_hours:.1f} ঘণ্টা = {battery_life_days:.1f} দিন")
pump_should_run সত্যি ছিল বলে) — অর্থাৎ এটি একটি রক্ষণশীল (worst-case) হিসাব। বাস্তবে মাটি
সবসময় শুকনো থাকবে না, তাই বেশিরভাগ চক্রে পাম্প নাও চলতে পারে — সেক্ষেত্রে প্রকৃত ব্যাটারি-লাইফ এখানের
হিসাবের চেয়ে বেশি হবে। এই সংখ্যাগুলো (কারেন্ট ড্র, ক্যাপাসিটি) সাধারণভাবে উল্লেখিত ইলাস্ট্রেটিভ মান,
নির্দিষ্ট কোনো প্রকৃত পণ্যের ডেটাশিট সংখ্যা নয়।
৭ · চূড়ান্ত সারাংশ
শেষ কোড সেলটি কোনো নতুন গণনা করছে না — এটি শুধু ধাপ ১-৬-এ ইতিমধ্যে গণনা করা প্রকৃত ভ্যারিয়েবলগুলো থেকে একটি মানুষ-পড়ার-উপযোগী সারাংশ তৈরি করছে, ঠিক যেমন একটি বাস্তব ডিভাইসের লগ/স্ট্যাটাস আউটপুট হতো।
# ---- চূড়ান্ত সারাংশ -- ধাপ ১-৬-এর প্রকৃত গণনা করা ফলাফল থেকে তৈরি, হার্ডকোডেড নয় ----
print("===== স্মার্ট প্ল্যান্ট-ওয়াটারিং কন্ট্রোলার -- চক্র সারাংশ =====")
print(f"মাটির আর্দ্রতা রিডিং: {measured_moisture_pct:.1f}% (I2C-এর মাধ্যমে যাচাই করা কোড: {i2c_value})")
print(f"পাম্প চালানো হয়েছে: {'হ্যাঁ' if pump_should_run else 'না'}, সময়কাল: {pump_active_s:.1f} সেকেন্ড "
f"({measured_duty_pct:.0f}% PWM ডিউটিতে)")
print(f"RTOS শিডিউলার: {TOTAL_TICKS} টিকে sensor_read={task_run_counts.get('sensor_read', 0)}, "
f"pump_control={task_run_counts.get('pump_control', 0)}, "
f"network_publish={task_run_counts.get('network_publish', 0)} বার চলেছে")
print(f"MQTT বার্তা ডেলিভার হয়েছে dashboard_bed1-এ: {mqtt_delivered}")
print(f"আনুমানিক ব্যাটারি-লাইফ: {battery_life_days:.1f} দিন "
f"(গড় কারেন্ট {average_current_mA:.3f} mA, ক্যাপাসিটি {BATTERY_CAPACITY_MAH} mAh)")
print("=" * 55)
এই একটিমাত্র সিমুলেশনে মডিউল ৫ (ADC, PWM), মডিউল ৬ (I2C), মডিউল ৭ (RTOS শিডিউলিং), মডিউল ৮ (পাওয়ার বাজেট), মডিউল ৯ (ক্যালিব্রেশন), ও মডিউল ১১ (MQTT) — একসাথে একটি একক, সুসংগত সিস্টেমে কাজ করেছে। বাস্তব এমবেডেড/IoT ইঞ্জিনিয়ারিং ঠিক এমনই — আলাদা আলাদা "টপিক" নয়, বরং একটি ডিভাইসের ভেতরে একসাথে বাঁধা অনেকগুলো স্তর, যেখানে একটি স্তরের সিদ্ধান্ত পরের স্তরকে সরাসরি প্রভাবিত করে। এখানেই Microprocessors, Embedded Systems & IoT কোর্সের ৫৭টি পাঠ সম্পূর্ণ হলো।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
যদি ধাপ ১-এ মাটি সত্যিকারভাবেই ভেজা থাকতো (ধরুন true_moisture_pct = 70.0), ধাপ ২, ৫
ও ৬-এর ফলাফলে কী পরিবর্তন আসতো বলে আপনার ধারণা?
measured_moisture_pct থ্রেশহোল্ড (৪০%)-এর অনেক উপরে থাকতো, তাই moisture_deficit
শূন্য হতো এবং pump_should_run = False হতো (ধাপ ২)। ধাপ ৫-এ MQTT-তে ran=False
পাবলিশ হতো, আর ধাপ ৬-এ pump_active_s = 0 হওয়ায় সেই পদটি চার্জ-গণনা থেকে বাদ পড়ে যেত —
ফলে গড় কারেন্ট কমে যেত এবং battery_life_days বেড়ে যেত, ঠিক যেমন ধাপ ৬-এর নোটে বলা
হয়েছে।
প্র ০২
ধাপ ৫-এ dashboard_bed2 কেন কোনো বার্তা পায়নি, যদিও এটি একই ব্রোকারে সাবস্ক্রাইব করা
আছে?
কারণ dashboard_bed2-এর টপিক ফিল্টার "farm/bed2/#", আর পাবলিশ হওয়া টপিক
"farm/bed1/moisture" ও "farm/bed1/pump" — দ্বিতীয় লেভেলে
bed2 != bed1, তাই topic_matches ফাংশনের লজিক অনুযায়ী এটি একটি প্রকৃত
অ-ম্যাচ (non-match), হার্ডকোডেড বাদ দেওয়া নয়। এটিই প্রমাণ করে ম্যাচিং যুক্তিটি সত্যিই কাজ করছে, শুধু
সবসময় "হ্যাঁ" বলছে না।
প্র ০৩
ধাপ ৪-এর RTOS ট্রেসে network_publish টাস্ক কখনো sensor_read বা
pump_control-কে প্রি-এম্পট (preempt) করে না কেন?
কারণ network_publish-এর প্রায়োরিটি সবচেয়ে কম (৩) — শিডিউলার প্রতি টিকে সব প্রস্তুত
টাস্কের মধ্যে সর্বোচ্চ-প্রায়োরিটি টাস্কটিই বেছে নেয় (ready_tasks.sort(key=lambda t: t.priority)),
তাই sensor_read বা pump_control প্রস্তুত থাকলে তারাই সবসময় আগে চলবে, আর
network_publish শুধু তখনই সুযোগ পাবে যখন উচ্চ-প্রায়োরিটি কোনো টাস্কের কাজ বাকি নেই।
অনুশীলন
-
চিন্তা করুন: ধাপ ৬-এর কোড সেলে
CYCLE_PERIOD_S-কে600-এর বদলে60(প্রতি ১ মিনিটে একবার চক্র) করলেaverage_current_mA-এর কী হবে বলে আপনার ধারণা?সক্রিয় সময় (
active_time_used_s) একই থাকবে, কিন্তু সেটি এখন অনেক ছোট একটি চক্র সময়ের ওপর ভাগ হবে (৬০ সেকেন্ড, ৬০০-এর বদলে) — তাই একই পরিমাণ চার্জ অনেক কম সময়ে খরচ হচ্ছে ধরে নেওয়ায়average_current_mAবাড়বে, এবংbattery_life_daysকমবে। -
পরীক্ষা করুন: ধাপ ৫-এ
dashboard_bed2-এর সাবস্ক্রিপশন বদলেbroker.subscribe("dashboard_bed2", "farm/+/moisture")করুন এবং Run চেপে দেখুনdelivered_moisture-এ এখনdashboard_bed2-ও যুক্ত হয় কিনা।হ্যাঁ যুক্ত হবে —
"farm/+/moisture"-এ+ঠিক এক লেভেল ম্যাচ করে (bed1বা যেকোনো একক লেভেল), আর তৃতীয় লেভেল আক্ষরিকভাবেmoisture-এর সাথে মিলে যায় — তাই"farm/bed1/moisture"টপিকটি এখন উভয় ফিল্টারের সাথেই ম্যাচ করবে, কিন্তু"farm/bed1/pump"এখনোdashboard_bed2-কে ডেলিভার হবে না, কারণ শেষ লেভেলpump != moisture।
অভিনন্দন — কোর্স সম্পূর্ণ হলো!
- সম্পূর্ণ ৫৭টি পাঠের সিলেবাস আবার দেখুন কোর্স সম্পূর্ণ মাইক্রোপ্রসেসর আর্কিটেকচার থেকে GPIO, টাইমার/PWM/ADC, সিরিয়াল প্রোটোকল, RTOS, পাওয়ার, সেন্সর/অ্যাকচুয়েটর, IoT আর্কিটেকচার, ওয়্যারলেস প্রোটোকল, MQTT/CoAP, সিকিউরিটি হয়ে এই ক্যাপস্টোন পর্যন্ত — পুরো কোর্সটি এখন সম্পূর্ণ।
- আরেকটি কোর্স চালিয়ে যান Design and Analysis of Algorithms অ্যালগরিদম ডিজাইন ও বিশ্লেষণের একটি সম্পূর্ণ কোর্স — আপনার শেখার যাত্রা চালিয়ে যেতে এখনই দেখুন।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture ও আরও অনেক কোর্স — সব এক জায়গায়।