From 697162e6690484f5135f63b26a61b579cd46e18c Mon Sep 17 00:00:00 2001 From: Divss72 Date: Wed, 20 May 2026 00:01:23 +0530 Subject: [PATCH] Add Semester 5 IoT course with 10 markdown modules. Integrates the full IoT masterclass into openCSE with markdown rendering, sidebar navigation, and live subject linking from the homepage. Co-authored-by: Cursor --- app/components/MarkdownNotes.tsx | 90 ++ app/components/subjects.tsx | 2 +- app/sem5/iot/[chapter]/page.tsx | 148 ++ app/sem5/iot/components/sidebar.tsx | 65 + app/sem5/iot/content/chapter0.tsx | 76 + app/sem5/iot/layout.tsx | 24 + app/sem5/iot/page.tsx | 8 + lib/iotChapters.ts | 67 + notes/iot/README.md | 26 + notes/iot/module-01-introduction.md | 192 +++ notes/iot/module-02-sensors-actuators.md | 177 +++ notes/iot/module-03-arduino.md | 183 +++ notes/iot/module-04-esp32.md | 204 +++ notes/iot/module-05-protocols.md | 209 +++ notes/iot/module-06-cloud.md | 205 +++ notes/iot/module-07-analytics.md | 177 +++ notes/iot/module-08-smart-automation.md | 184 +++ notes/iot/module-09-iiot-security.md | 190 +++ notes/iot/module-10-capstone.md | 199 +++ package-lock.json | 1600 +++++++++++++++++++++- package.json | 4 +- public/iot/images/.gitkeep | 0 22 files changed, 3960 insertions(+), 70 deletions(-) create mode 100644 app/components/MarkdownNotes.tsx create mode 100644 app/sem5/iot/[chapter]/page.tsx create mode 100644 app/sem5/iot/components/sidebar.tsx create mode 100644 app/sem5/iot/content/chapter0.tsx create mode 100644 app/sem5/iot/layout.tsx create mode 100644 app/sem5/iot/page.tsx create mode 100644 lib/iotChapters.ts create mode 100644 notes/iot/README.md create mode 100644 notes/iot/module-01-introduction.md create mode 100644 notes/iot/module-02-sensors-actuators.md create mode 100644 notes/iot/module-03-arduino.md create mode 100644 notes/iot/module-04-esp32.md create mode 100644 notes/iot/module-05-protocols.md create mode 100644 notes/iot/module-06-cloud.md create mode 100644 notes/iot/module-07-analytics.md create mode 100644 notes/iot/module-08-smart-automation.md create mode 100644 notes/iot/module-09-iiot-security.md create mode 100644 notes/iot/module-10-capstone.md create mode 100644 public/iot/images/.gitkeep diff --git a/app/components/MarkdownNotes.tsx b/app/components/MarkdownNotes.tsx new file mode 100644 index 0000000..b6dddff --- /dev/null +++ b/app/components/MarkdownNotes.tsx @@ -0,0 +1,90 @@ +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import type { Components } from "react-markdown"; + +const markdownComponents: Components = { + h1: ({ children }) => ( +

{children}

+ ), + h2: ({ children }) => ( +

{children}

+ ), + h3: ({ children }) => ( +

{children}

+ ), + p: ({ children }) =>

{children}

, + ul: ({ children }) => , + ol: ({ children }) => ( +
    {children}
+ ), + li: ({ children }) =>
  • {children}
  • , + blockquote: ({ children }) => ( +
    + {children} +
    + ), + pre: ({ children }) => ( +
    +      {children}
    +    
    + ), + code: ({ className, children }) => { + const isBlock = Boolean(className); + if (isBlock) { + return {children}; + } + return ( + + {children} + + ); + }, + img: ({ src, alt }) => ( + // eslint-disable-next-line @next/next/no-img-element + {alt + ), + table: ({ children }) => ( +
    + + {children} +
    +
    + ), + th: ({ children }) => ( + + {children} + + ), + td: ({ children }) => ( + {children} + ), + hr: () =>
    , + a: ({ href, children }) => ( + + {children} + + ), +}; + +type MarkdownNotesProps = { + content: string; +}; + +export function MarkdownNotes({ content }: MarkdownNotesProps) { + return ( +
    + + {content} + +
    + ); +} diff --git a/app/components/subjects.tsx b/app/components/subjects.tsx index d586e86..f09e6f2 100644 --- a/app/components/subjects.tsx +++ b/app/components/subjects.tsx @@ -125,7 +125,7 @@ const subjectCodes: Record = { }; // Available subjects -const available = ["ep", "c", "em1", "em2", "oops", "dsc", "os", "ml", "dops"]; +const available = ["ep", "c", "em1", "em2", "oops", "dsc", "os", "ml", "dops", "iot"]; export default function SubjectsSection() { return ( diff --git a/app/sem5/iot/[chapter]/page.tsx b/app/sem5/iot/[chapter]/page.tsx new file mode 100644 index 0000000..c615359 --- /dev/null +++ b/app/sem5/iot/[chapter]/page.tsx @@ -0,0 +1,148 @@ +import fs from "fs/promises"; +import Link from "next/link"; +import { Metadata } from "next"; +import { Righteous } from "next/font/google"; +import { ArrowBigLeft, ArrowBigRight } from "lucide-react"; + +import { MarkdownNotes } from "@/app/components/MarkdownNotes"; +import { + getIotChapter, + iotChapters, + iotNotesDir, +} from "@/lib/iotChapters"; +import { Ch0Content } from "../content/chapter0"; + +const righteous = Righteous({ + subsets: ["latin"], + weight: "400", + variable: "--font-righteous", +}); + +type ChapterProps = { + params: Promise<{ chapter: string }>; +}; + +export async function generateMetadata({ + params, +}: ChapterProps): Promise { + const { chapter: chapterId } = await params; + const chapter = getIotChapter(chapterId); + const title = chapter + ? `${chapter.title} | IoT | openCSE` + : "Internet of Things | openCSE"; + return { title }; +} + +async function loadMarkdown(fileName: string) { + const filePath = `${iotNotesDir}/${fileName}`; + return fs.readFile(filePath, "utf8"); +} + +export default async function ChapterPage({ params }: ChapterProps) { + const { chapter: chapterId } = await params; + const currentIndex = iotChapters.findIndex((c) => c.id === chapterId); + const chapter = iotChapters[currentIndex]; + + if (!chapter) { + return ( +
    +

    Module not found

    + + Return to Course Outline + +
    + ); + } + + const prevChapter = currentIndex > 0 ? iotChapters[currentIndex - 1] : null; + const nextChapter = + currentIndex < iotChapters.length - 1 + ? iotChapters[currentIndex + 1] + : null; + + let body: React.ReactNode; + if (chapter.id === "ch0") { + body = ; + } else if (chapter.markdownFile) { + const markdown = await loadMarkdown(chapter.markdownFile); + body = ; + } else { + body =

    Content coming soon.

    ; + } + + const navLinkClass = + "px-4 py-1 text-2xl flex items-center justify-center bg-[#e2d1c1] text-[#1b0d00] rounded hover:bg-[#ac9e91] transition"; + + return ( +
    +
    +

    + Internet of Things (IoT) +

    + +

    + {chapter.title} +

    + +
    + {prevChapter ? ( + + Previous + + ) : ( +
    + )} + + {nextChapter ? ( + + Next + + ) : ( +
    + )} +
    + +
    + {body} +
    + +
    + {prevChapter ? ( + + {prevChapter.title} + + ) : ( +
    + )} + + {nextChapter ? ( + + {nextChapter.title}{" "} + + + ) : ( +
    + )} +
    +
    + ); +} diff --git a/app/sem5/iot/components/sidebar.tsx b/app/sem5/iot/components/sidebar.tsx new file mode 100644 index 0000000..2fd2686 --- /dev/null +++ b/app/sem5/iot/components/sidebar.tsx @@ -0,0 +1,65 @@ +"use client"; + +import { Righteous } from "next/font/google"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useState } from "react"; + +import { iotChapters } from "@/lib/iotChapters"; + +const righteous = Righteous({ + subsets: ["latin"], + weight: "400", + variable: "--font-righteous", +}); + +export default function Sidebar() { + const pathname = usePathname(); + const [open, setOpen] = useState(true); + + return ( +
    + + + +
    + ); +} diff --git a/app/sem5/iot/content/chapter0.tsx b/app/sem5/iot/content/chapter0.tsx new file mode 100644 index 0000000..5557e72 --- /dev/null +++ b/app/sem5/iot/content/chapter0.tsx @@ -0,0 +1,76 @@ +export const Ch0Content = () => { + return ( +
    +

    + Welcome to the Internet of Things (IoT){" "} + masterclass on openCSE. This 10-module engineering-first curriculum takes you + from basic sensor reads to industrial cloud deployments, smart automation, and + capstone system design. +

    + +
    +

    Module I: Introduction to IoT

    +
      +
    • IoT architecture: Sensor → Controller → Cloud → Action
    • +
    • Human-body analogy for system design
    • +
    • First Arduino/ESP32 project: LED blink & serial monitor
    • +
    +
    + +
    +

    Module II: Sensors & Actuators

    +
      +
    • Analog vs digital sensors, voltage dividers
    • +
    • PIR, ultrasonic, relays, and motors
    • +
    • Reactive edge-computing projects
    • +
    +
    + +
    +

    Module III: Microcontrollers (Arduino)

    +
      +
    • Pin modes, PWM, and non-blocking code
    • +
    • LDR light sensors and servo control
    • +
    +
    + +
    +

    Module IV: Advanced Controllers (ESP32)

    +
      +
    • Wi-Fi, web server dashboards, OTA updates
    • +
    • Relay-controlled appliances
    • +
    +
    + +
    +

    Module V: Communication Protocols

    +
      +
    • UART, I2C, SPI, RFID, and MQTT pub/sub
    • +
    +
    + +
    +

    Module VI: Cloud Integration

    +
      +
    • ThingSpeak, REST APIs, and remote dashboards
    • +
    • DHT11 environmental logging
    • +
    +
    + +
    +

    Modules VII–X: Analytics to Capstone

    +
      +
    • Edge analytics and smart irrigation (Module 7)
    • +
    • Smart home automation with gas detection (Module 8)
    • +
    • Industrial IoT, TLS security, thermocouples (Module 9)
    • +
    • Multi-sensor health monitoring capstone (Module 10)
    • +
    +
    + +

    + Use the sidebar to open each module. Content is written for hands-on labs with + Arduino IDE or PlatformIO and common hobbyist hardware. +

    +
    + ); +}; diff --git a/app/sem5/iot/layout.tsx b/app/sem5/iot/layout.tsx new file mode 100644 index 0000000..98cc30d --- /dev/null +++ b/app/sem5/iot/layout.tsx @@ -0,0 +1,24 @@ +import Navbar from "../../components/navbar"; +import Sidebar from "./components/sidebar"; + +export const metadata = { + title: "Internet of Things (IoT) | openCSE", + description: + "Beginner-friendly IoT course notes: sensors, Arduino, ESP32, MQTT, cloud, and industrial projects for openCSE", +}; + +export default function IotLayout({ children }: { children: React.ReactNode }) { + return ( +
    + + +
    + + +
    +
    {children}
    +
    +
    +
    + ); +} diff --git a/app/sem5/iot/page.tsx b/app/sem5/iot/page.tsx new file mode 100644 index 0000000..0a8686a --- /dev/null +++ b/app/sem5/iot/page.tsx @@ -0,0 +1,8 @@ +export default function IotHome() { + return ( +
    +

    Internet of Things (IoT)

    +

    Select a module from the sidebar to get started.

    +
    + ); +} diff --git a/lib/iotChapters.ts b/lib/iotChapters.ts new file mode 100644 index 0000000..e153e69 --- /dev/null +++ b/lib/iotChapters.ts @@ -0,0 +1,67 @@ +import path from "path"; + +export type IotChapter = { + id: string; + title: string; + markdownFile?: string; +}; + +export const iotChapters: IotChapter[] = [ + { id: "ch0", title: "Course Outline" }, + { + id: "ch1", + title: "Introduction to IoT", + markdownFile: "module-01-introduction.md", + }, + { + id: "ch2", + title: "Sensors & Actuators", + markdownFile: "module-02-sensors-actuators.md", + }, + { + id: "ch3", + title: "Microcontrollers (Arduino)", + markdownFile: "module-03-arduino.md", + }, + { + id: "ch4", + title: "Advanced Controllers (ESP32)", + markdownFile: "module-04-esp32.md", + }, + { + id: "ch5", + title: "Communication Protocols", + markdownFile: "module-05-protocols.md", + }, + { + id: "ch6", + title: "Cloud Integration", + markdownFile: "module-06-cloud.md", + }, + { + id: "ch7", + title: "Data Analytics", + markdownFile: "module-07-analytics.md", + }, + { + id: "ch8", + title: "Smart Home Automation", + markdownFile: "module-08-smart-automation.md", + }, + { + id: "ch9", + title: "Industrial IoT & Security", + markdownFile: "module-09-iiot-security.md", + }, + { + id: "ch10", + title: "Capstone Projects", + markdownFile: "module-10-capstone.md", + }, +]; + +export const iotNotesDir = path.join(process.cwd(), "notes", "iot"); + +export function getIotChapter(chapterId: string) { + return iotChapters.find((chapter) => chapter.id === chapterId); +} diff --git a/notes/iot/README.md b/notes/iot/README.md new file mode 100644 index 0000000..9661aae --- /dev/null +++ b/notes/iot/README.md @@ -0,0 +1,26 @@ +# IoT Course Notes + +Ten-module Internet of Things curriculum for openCSE (Semester 5). + +## Modules + +| File | Topic | +|------|--------| +| `module-01-introduction.md` | IoT fundamentals & first Arduino project | +| `module-02-sensors-actuators.md` | Sensors, actuators, edge logic | +| `module-03-arduino.md` | Arduino, PWM, non-blocking code | +| `module-04-esp32.md` | ESP32, Wi-Fi, web UI | +| `module-05-protocols.md` | UART, I2C, SPI, MQTT | +| `module-06-cloud.md` | Cloud logging & dashboards | +| `module-07-analytics.md` | Edge analytics & irrigation | +| `module-08-smart-automation.md` | Smart home automation | +| `module-09-iiot-security.md` | Industrial IoT & TLS | +| `module-10-capstone.md` | Multi-sensor capstone | + +## Images + +Diagrams are referenced under `/iot/images/` (see `public/iot/images/`). Modules 7–10 use placeholder filenames; add PNG assets there to complete visuals. + +## Rendering + +Notes are rendered on `/sem5/iot/ch1`–`ch10` via `react-markdown` and `remark-gfm`. GitHub-style alerts (`> [!WARNING]`) appear as styled blockquotes. diff --git a/notes/iot/module-01-introduction.md b/notes/iot/module-01-introduction.md new file mode 100644 index 0000000..9241d53 --- /dev/null +++ b/notes/iot/module-01-introduction.md @@ -0,0 +1,192 @@ +# Module 1: Introduction to the Internet of Things (IoT) + +## 1. Introduction +Welcome to the world of the Internet of Things (IoT). For decades, the internet was a network of computers. Humans had to manually type in data to tell the internet what was happening in the real world. IoT changes this entirely. + +IoT is the concept of connecting physical objects—"things"—to the internet, allowing them to sense their environment, communicate data, and take automated actions without human intervention. From smart homes that adjust the temperature before you arrive, to massive industrial plants that predict machine failures before they happen, IoT is the bridge between the physical and digital worlds. + +In this course, we will not just learn the theory. We will build practical, engineering-focused systems from scratch, transitioning from basic sensor reads to full-scale industrial cloud deployments. + +--- + +## 2. Learning Objectives +By the end of this module, you will be able to: +- Define the Internet of Things and its core components. +- Understand the 4-stage IoT architecture: `Sensor → Controller → Cloud → Action`. +- Draw parallels between an IoT system and the human biological system to easily grasp architectures. +- Set up a fundamental microcontroller environment and write your first "Hello World" IoT code. + +--- + +## 3. Core Concepts & The Human Body Analogy + +To understand IoT, let’s compare an IoT system to the **Human Body**. This analogy is crucial for understanding how data flows in physical computing. + +### 1. Sensors (The Senses) +Just as your eyes, ears, and skin detect light, sound, and temperature, **Sensors** are the eyes and ears of an IoT system. They collect analog data from the physical world (e.g., temperature, motion, humidity) and convert it into electrical signals. +*Example: A DHT11 Temperature Sensor.* + +### 2. Microcontrollers (The Brain) +When your hand touches a hot surface, your nerves send a signal to your brain. Your brain processes this information and decides what to do. In IoT, the **Microcontroller** (like an Arduino or ESP32) acts as the brain. It receives electrical signals from the sensors, processes the data using code, and makes decisions. +*Example: An ESP32 Wi-Fi Chip.* + +### 3. The Network & Cloud (The Hive Mind / Memory) +Imagine if your brain could instantly share its experiences with thousands of other brains globally. The **Network/Cloud** acts as this global memory and processing center. It stores historical data, runs heavy analytics (like machine learning), and connects different "brains" together over the internet via Wi-Fi, Cellular, or LoRa protocols. +*Example: AWS IoT or a ThingSpeak Dashboard.* + +### 4. Actuators (The Muscles) +Once your brain decides a surface is too hot, it sends a signal to your muscles to pull your hand away. In IoT, **Actuators** are the muscles. They receive commands from the microcontroller and perform physical actions, such as turning on a motor, switching a relay, or sounding an alarm. +*Example: A 5V Relay switching on a water pump.* + +--- + +## 4. The Standard IoT Data Flow + +Every IoT system, no matter how complex, follows this fundamental data flow: + +```mermaid +flowchart LR + A[Sensors
    (Data Collection)] --> B[Microcontroller
    (Local Processing)] + B --> C[Network/Gateway
    (Transmission)] + C --> D[Cloud/Server
    (Storage & Analytics)] + D -.-> E[User Dashboard / App] + D --> F[Actuators
    (Physical Response)] + B --> F +``` + +> [!NOTE] +> **Edge vs. Cloud Computing** +> Notice how the Microcontroller can trigger Actuators *directly* without going to the cloud. This is called **Edge Computing**. We do this for critical systems (like a fire alarm) where waiting for an internet response would be too slow or dangerous. + +--- + +## 5. Real-World Examples: Where is IoT used? + +Let's look at how the `Sensor → Controller → Cloud → Action` flow applies to real engineering problems: + +### 1. Smart Agriculture (Precision Farming) +- **Sensor**: Soil moisture sensors detect dry soil. +- **Controller**: A local node reads the data and sends it over a long-range network (LoRa). +- **Cloud**: The server checks the weather forecast API. If it's going to rain tomorrow, it decides *not* to water the crops, saving money. +- **Action**: If no rain is expected, it sends a command to a relay (Actuator) to turn on the water pumps. + +### 2. Predictive Maintenance in Industry (IIoT) +- **Sensor**: Vibration sensors on factory motors. +- **Controller**: Collects thousands of vibration data points per second. +- **Cloud**: AI algorithms analyze the vibration frequencies. They detect an anomaly matching a worn-out bearing, predicting a failure 2 weeks before it happens. +- **Action**: Alerts the maintenance dashboard to schedule a repair before the machine breaks, saving millions in downtime. + +--- + +## 6. Practical Engineering Insights + +When building IoT systems, beginners often think the code is the hardest part. In reality, **System Integration** is the hardest part. + +- **Power is everything**: A Wi-Fi chip sending data every second will drain a battery in hours. Real engineering involves writing code that puts the chip to "Deep Sleep" for 99% of the time, waking up only to send data. +- **The Internet is unreliable**: You must always write code assuming the Wi-Fi will disconnect. What happens to your smart medical device if the router reboots? Your local controller must have fallback logic. +- **Security is not optional**: If your Smart Home controller uses a default password, a botnet can hijack it. We will cover IoT security deeply in Module 9. + +--- + +## 7. Common Mistakes for Beginners & Visual Troubleshooting + +> [!WARNING] +> - **Ignoring Voltage Differences**: Connecting a 5V sensor to a 3.3V microcontroller pin will burn out the pin. Always check voltage logic levels! +> - **Blocking Code (`delay()`)**: Using commands like `delay(5000)` stops the entire processor for 5 seconds. If a fire starts during those 5 seconds, the sensor won't read it. We will learn "non-blocking" code later. +> - **Forgetting Ground (GND)**: Electricity needs a complete circuit. If you power a sensor from a separate battery, you MUST connect the sensor's Ground to the microcontroller's Ground. + +### Visual Troubleshooting: Missing Ground +![Disconnected Ground Wire Troubleshooting](/iot/images/m1-troubleshooting.png) +*Notice the disconnected ground wire. This is the #1 cause of sensors returning garbage data or failing to power on entirely. Always ensure a common ground!* + +--- + +## 8. Interview-Style Conceptual Questions + +Test your understanding before moving to the project: + +1. **If a Smart Thermostat loses internet connection, should it turn off the heating system?** + *Answer: No. It should fall back to Edge Computing, using its local microcontroller and sensors to maintain a safe default temperature until the connection returns.* +2. **What is the difference between an Actuator and a Sensor?** + *Answer: A sensor converts physical phenomena into electrical data (Input). An actuator converts electrical signals into physical action (Output).* +3. **Why do we need a Cloud if the Microcontroller can process data locally?** + *Answer: Microcontrollers have very limited memory (often kilobytes) and processing power. The cloud provides infinite storage for historical trends, heavy machine learning analytics, and global remote access.* + +--- + +## 9. Practical Project: LED Blink & Serial Monitor "Hello World" + +It's time to build your first IoT-ready node. We will use an Arduino or an ESP32 (the industry standard microcontrollers). + +### Project Overview +We will configure the microcontroller, write a script to control an onboard LED (our first Actuator), and send text data back to our computer via the Serial Monitor (simulating basic communication). + +### Required Components +- 1x Arduino UNO or ESP32 Development Board +- 1x USB Cable +- Laptop with Arduino IDE installed (C++ environment) + +### Step-by-Step Implementation & IDE Visual Guide + +![Arduino IDE COM Port Setup](/iot/images/m1-ide-setup.png) +*Visual Guide: Selecting the correct COM Port is critical. Without this, your code cannot upload to the physical board.* + +**Step 1: Install the IDE & Select COM Port** +Download the Arduino IDE from arduino.cc. Connect your board via USB. Open the IDE, navigate to `Tools -> Board` and select your board type. Then go to `Tools -> Port` and select the COM port that appeared when you plugged in the board. + +**Step 2: Circuit Wiring** +![Arduino Uno LED Wiring Diagram](/iot/images/m1-arduino-wiring.png) +*Visual Connection Flow: Arduino Pin 13 → 220 Ohm Resistor → LED Positive Leg → LED Negative Leg → Arduino GND. The resistor prevents the LED from drawing too much current and burning out.* + +**Step 3: The Code Structure** +All C++ code for these controllers has two main functions: +- `setup()`: Runs exactly *once* when the board turns on. Used to configure pins. +- `loop()`: Runs continuously forever. Used to read sensors and trigger actions. + +**Step 3: Writing the Code** + +```cpp +// Define the pin where our LED is connected +// Arduino Uno usually has a built-in LED on pin 13. +const int ledPin = 13; + +void setup() { + // 1. Start communication with the computer at 9600 bits per second + Serial.begin(9600); + + // 2. Configure the LED pin as an OUTPUT (Actuator) + pinMode(ledPin, OUTPUT); + + // Send our first message + Serial.println("System Booting... IoT Node Online."); +} + +void loop() { + // Send power to the LED to turn it ON + digitalWrite(ledPin, HIGH); + Serial.println("Status: LED is ON"); + delay(1000); // Wait for 1 second (1000 milliseconds) + + // Cut power to the LED to turn it OFF + digitalWrite(ledPin, LOW); + Serial.println("Status: LED is OFF"); + delay(1000); // Wait for 1 second +} +``` + +### Code Explanation & Hardware Interaction +1. `Serial.begin(9600);` opens a communication channel over the USB cable. This is the precursor to sending data over Wi-Fi! +2. `pinMode(ledPin, OUTPUT);` tells the microcontroller's hardware to prepare that specific physical pin to push electrical current out. If it was a sensor, we would set it to `INPUT`. +3. `digitalWrite(ledPin, HIGH);` physically connects the pin to 5 Volts (or 3.3V) internally, completing the circuit to light the LED. + +### Testing & Troubleshooting +- **Upload the code**: Click the right-arrow (Upload) button in the IDE. +- **Open Serial Monitor**: Click the magnifying glass icon in the top right. Set the baud rate to `9600`. +- **Expected Result**: The onboard LED will blink every second, and your screen will stream the ON/OFF status text. +- **Troubleshooting**: If you see garbage characters in the monitor, ensure the dropdown in the bottom right of the Serial Monitor is set to `9600 baud` to match `Serial.begin(9600);`. + +### Future Improvements +In the next module, we will replace the `delay()` function with a real physical button and a motion sensor, upgrading from a "dumb" timed loop to a reactive edge-computing system. + +--- +**[End of Module 1]** diff --git a/notes/iot/module-02-sensors-actuators.md b/notes/iot/module-02-sensors-actuators.md new file mode 100644 index 0000000..55afda7 --- /dev/null +++ b/notes/iot/module-02-sensors-actuators.md @@ -0,0 +1,177 @@ +# Module 2: The Physical Layer - Sensors & Actuators + +## 1. Introduction +In Module 1, we learned that sensors are the "eyes and ears" of an IoT system, while actuators are the "muscles". In this module, we will zoom in on the physical layer. How exactly does a microcontroller understand the physical world? The answer lies in **voltage**. + +Microcontrollers cannot feel heat or see light. They can only measure electricity. Therefore, every sensor in the world is simply a device that converts a physical property (like heat, light, or pressure) into an electrical voltage. + +--- + +## 2. Learning Objectives +By the end of this module, you will: +- Understand the critical difference between **Analog** and **Digital** signals. +- Know how common sensors (PIR, Ultrasonic, DHT11) and actuators (Relays, Buzzers) operate. +- Understand hardware fundamentals like breadboarding and pull-up resistors. +- Build a dual-sensor edge-computing system: A Motion Detection Alarm. + +--- + +## 3. Core Concepts: Analog vs. Digital Signals + +Before wiring any sensor, you must know what "language" it speaks. All sensors output data as either an Analog or a Digital signal. + +### Digital Signals (The Binary World) +A digital signal has only two states: **HIGH (1)** or **LOW (0)**. +- In a 5V system, HIGH usually means 5 Volts, and LOW means 0 Volts. +- *Analogy*: A light switch. It is either completely ON or completely OFF. +- *Sensors*: Push buttons, PIR (Motion) sensors, Magnetic Door switches. + +### Analog Signals (The Continuous World) +An analog signal can be any value between a minimum and maximum range. +- Instead of just 0V or 5V, an analog signal could be 1.2V, 3.7V, or 4.9V. +- *Analogy*: A volume dial on a radio. You can turn it smoothly from 0 to 10. +- *Sensors*: LDR (Light Dependent Resistors), Soil Moisture sensors, Potentiometers. + +> [!IMPORTANT] +> **The ADC (Analog to Digital Converter)** +> Microcontrollers process binary (1s and 0s). They cannot directly understand 3.7 Volts. To solve this, microcontrollers have built-in ADCs. An ADC takes an analog voltage and converts it into a digital number (e.g., 0V = 0, 5V = 1023). When wiring an analog sensor, you MUST plug it into a specific "Analog In" pin (like `A0`). + +--- + +## 4. Hardware Understanding: Breadboards & Resistors + +To build prototypes, engineers use **Breadboards**. + +A breadboard is a plastic board with holes. Underneath the holes are metal clips that connect certain holes together. +- **Power Rails**: The long red (+) and blue/black (-) lines on the edges. All holes in the red line are connected horizontally. Used to distribute power. +- **Terminal Strips**: The middle sections. Holes are connected vertically in rows of 5. Used to connect components together without soldering. + +### The Pull-up / Pull-down Resistor (Avoiding Floating States) +Imagine a push button connected to a digital pin. When pressed, it connects the pin to 5V (HIGH). But what happens when it's NOT pressed? The pin is connected to *nothing*. + +In electronics, "nothing" does not mean 0V. An unconnected pin acts like an antenna, picking up static electricity from the air, rapidly flipping between HIGH and LOW. This is called a **Floating State**. +To fix this, we use a resistor to "pull" the pin down to Ground (0V) when the button isn't pressed. + +--- + +## 5. Industrial Use Cases of Common Sensors & Hardware Spotlights + +### Hardware Spotlight: PIR (Passive Infrared) Sensor +![PIR Sensor Module](/iot/images/m2-pir-sensor.png) +*The HC-SR501 PIR Sensor. Notice the three pins: VCC (5V Power), OUT (Digital Signal), and GND (Ground). The white dome is a Fresnel lens that widens the detection angle.* + +1. **PIR (Passive Infrared) Sensor**: + - *How it works*: Detects infrared radiation emitted by body heat. + - *Real-world*: Security systems, automatic sliding doors, energy-saving office lights. +2. **Ultrasonic Sensor (HC-SR04)**: + - *How it works*: Emits a high-frequency sound pulse and measures how long it takes for the echo to bounce back, calculating distance. + - *Real-world*: Car reversing sensors, industrial liquid level measurement in silos. +3. **Relay Module (Actuator)**: + - *How it works*: An electromagnetic switch. A tiny 5V signal from a microcontroller turns on an electromagnet, which pulls a heavy-duty switch closed, allowing 220V AC mains power to flow. + - *Real-world*: Smart plugs, automated industrial pumps, HVAC control systems. + +--- + +## 6. Interview-Style Conceptual Questions + +1. **Why can't you connect an LDR (Light Sensor) to a Digital Pin?** + *Answer: An LDR outputs a varying voltage depending on light intensity (Analog). A digital pin can only read HIGH or LOW. It would just read "HIGH" for almost all light levels, losing the gradient data.* +2. **You want to turn on a 220V water heater using an Arduino. How do you do this safely?** + *Answer: You must use a Relay or a Solid State Relay (SSR). The Arduino operates at 5V DC and will explode if exposed to 220V AC. The relay acts as an electrically isolated switch.* + +--- + +## 7. Practical Project: Smart Motion Detection Alarm + +Let's build a practical Edge-Computing device: An alarm system that sounds a buzzer when motion is detected, but *only* if the object is closer than 50 cm. + +### Working Principle +We will combine a PIR Sensor (Digital) and an Ultrasonic Sensor (Digital Timing) with a Buzzer (Actuator). The microcontroller will run logic: `IF motion == true AND distance < 50cm THEN trigger_alarm()`. + +### Required Components +- 1x Microcontroller (Arduino Uno/ESP32) +- 1x PIR Motion Sensor +- 1x HC-SR04 Ultrasonic Sensor +- 1x Active Buzzer +- Breadboard & Jumper Wires + +### Circuit Connection Explanation & Visual Layout +![Smart Alarm Breadboard Wiring](/iot/images/m2-wiring.png) +*Visual Connection Flow: Arduino 5V and GND power the breadboard rails. The PIR and Ultrasonic sensors draw power from these rails. The sensor signal pins go back to the Arduino's Digital inputs. The Arduino Digital pin 8 drives the buzzer.* + +1. **Power**: Connect 5V and GND from Arduino to the breadboard power rails. +2. **PIR Sensor**: VCC to 5V, GND to GND, OUT to Arduino Digital Pin `2`. +3. **Ultrasonic Sensor**: VCC to 5V, GND to GND, TRIG to Digital Pin `9`, ECHO to Digital Pin `10`. +4. **Buzzer**: Positive leg to Digital Pin `8`, Negative leg to GND. + +### Step-by-Step Code Implementation + +```cpp +// Define Pins +const int pirPin = 2; +const int trigPin = 9; +const int echoPin = 10; +const int buzzerPin = 8; + +void setup() { + Serial.begin(9600); + + // Set Pin Modes + pinMode(pirPin, INPUT); + pinMode(trigPin, OUTPUT); + pinMode(echoPin, INPUT); + pinMode(buzzerPin, OUTPUT); + + Serial.println("Alarm System Initializing..."); + delay(5000); // Give the PIR sensor 5 seconds to calibrate to room temp + Serial.println("System Active."); +} + +void loop() { + // 1. Read the PIR Sensor + int motionDetected = digitalRead(pirPin); + + // 2. Measure Distance using Ultrasonic + // Send a 10 microsecond pulse + digitalWrite(trigPin, LOW); + delayMicroseconds(2); + digitalWrite(trigPin, HIGH); + delayMicroseconds(10); + digitalWrite(trigPin, LOW); + + // Read how long the echo took + long duration = pulseIn(echoPin, HIGH); + + // Convert time to distance in cm (Speed of sound = 343m/s) + int distanceCm = duration * 0.034 / 2; + + // 3. Processing Logic (Edge Computing) + if (motionDetected == HIGH && distanceCm < 50 && distanceCm > 0) { + Serial.println("INTRUDER ALERT! Proximity: " + String(distanceCm) + "cm"); + digitalWrite(buzzerPin, HIGH); // Turn on Alarm + delay(2000); // Sound for 2 seconds + } else { + digitalWrite(buzzerPin, LOW); // Turn off Alarm + } + + delay(100); // Short delay for stability +} +``` + +### Code Explanation +- `pulseIn(echoPin, HIGH)`: This is a special function. It waits for the `echoPin` to go HIGH, starts a stopwatch, and stops the stopwatch when it goes LOW. This gives us the exact time the sound wave spent in the air. +- `distanceCm = duration * 0.034 / 2`: Sound travels at 0.034 cm per microsecond. We divide by 2 because the sound wave traveled to the object AND back. + +### Visual Troubleshooting +![Reversed Polarity Error](/iot/images/m2-troubleshooting.png) +*CRITICAL ERROR: Reversed Polarity. The red VCC wire is connected to Ground, and the black GND wire is connected to 5V. Doing this will instantly destroy the sensor, cause it to smoke, or even permanently damage the Arduino's voltage regulator. ALWAYS double-check red and black wires before plugging in USB power.* + +- **False Alarms**: PIR sensors are very sensitive. Ensure it is not pointing at a window (sunlight contains infrared) or a heater. +- **Distance reads 0 or negatives**: Check the TRIG and ECHO wiring. If they are swapped, the `pulseIn` function will timeout. +- **Sensor getting extremely hot**: Unplug it immediately! You have reversed VCC and GND (as seen in the image above). + +### Future Improvements +Right now, the alarm only sounds locally. If you are not home, you won't hear it. In the upcoming modules, we will replace the Arduino with an ESP32 Wi-Fi chip to send a push notification to your phone when the alarm triggers. + +--- +**[End of Module 2]** diff --git a/notes/iot/module-03-arduino.md b/notes/iot/module-03-arduino.md new file mode 100644 index 0000000..b8822c2 --- /dev/null +++ b/notes/iot/module-03-arduino.md @@ -0,0 +1,183 @@ +# Module 3: Microcontroller Fundamentals (The Brain) + +## 1. Introduction +We have sensors to collect data and actuators to perform physical tasks. But how do we connect them? If an LDR (Light Sensor) detects that it is dark, how does a streetlight know it should turn on? + +This logic is executed by a **Microcontroller**. A microcontroller is essentially a tiny, self-contained computer on a single chip. It contains a processor (CPU), memory (RAM & Flash), and programmable Input/Output pins (GPIO). In this module, we will explore the architecture of these chips using the world's most popular educational platform: **The Arduino**. + +--- + +## 2. Learning Objectives +By the end of this module, you will: +- Understand the architecture of a microcontroller (CPU, Memory, GPIO). +- Master the difference between Digital I/O, Analog In (ADC), and PWM (Fake Analog Out). +- Understand the C++ logic flow: `setup()`, `loop()`, and conditionals. +- Build a Smart Street Light system that automatically fades LEDs based on ambient sunlight. + +--- + +## 3. Core Concepts: The Anatomy of a Microcontroller + +While there are thousands of microcontrollers (AVR, ARM, PIC), they all share a similar architecture. Let's look at the Arduino Uno (which uses the ATmega328P chip): + +1. **The Processor (CPU)**: The brain. It executes your C++ code instructions. The Arduino Uno runs at 16 MHz, meaning it can execute 16 million instructions per second. (Your laptop runs at ~3 GHz, nearly 200 times faster, but for IoT, 16 MHz is plenty!). +2. **Flash Memory (Hard Drive)**: Where your code is permanently stored. Even if you unplug the battery, the code remains. +3. **SRAM (RAM)**: Temporary memory used to store variables while the code is running. If power is lost, this memory is wiped. +4. **GPIO (General Purpose Input/Output) Pins**: The physical metal legs on the chip where you connect your sensors and actuators. + +### Hardware Spotlight: Arduino Uno Pinout +![Arduino Uno Pinout](/iot/images/m3-arduino-pinout.png) +*Notice the digital pins on the top right (0-13), the analog pins on the bottom right (A0-A5), and the power distribution pins on the bottom left (5V, 3.3V, GND).* + +### The Three Types of GPIO Pins + +Understanding these three pin types is the most important skill in IoT hardware: + +#### 1. Digital I/O (Input / Output) +Can only be `HIGH` (5V) or `LOW` (0V). +- *Input*: Reading a push button. +- *Output*: Turning a Relay ON or OFF. + +#### 2. Analog In (ADC - Analog to Digital Converter) +Can read voltages between 0V and 5V and convert them to a number. On a 10-bit ADC (like Arduino), 0V = `0` and 5V = `1023`. +- *Input*: Reading a temperature sensor or a volume knob. + +#### 3. PWM (Pulse Width Modulation) +Microcontrollers cannot output a true analog voltage (like 2.5V). They can only output 0V or 5V. So how do we dim an LED or control the speed of a motor? We use **PWM**. +PWM rapidly turns a Digital pin HIGH and LOW thousands of times a second. +- If it is HIGH 50% of the time and LOW 50% of the time, the *average* voltage is 2.5V. To the human eye, an LED looks 50% as bright! +- *Output*: Dimming lights, controlling servo motor angles, adjusting DC motor speeds. + +--- + +## 4. Software Understanding: The C++ Logic Flow + +IoT code is not like writing a web app or a Python script that runs once and exits. IoT code is designed to run **forever**. + +### The Infinite Loop +```cpp +void setup() { + // 1. Initialization. Runs ONCE on boot. + // Set pin modes, start Serial monitors, connect to Wi-Fi. +} + +void loop() { + // 2. The Engine. Runs FOREVER, thousands of times a second. + // Read sensors -> Make decision -> Trigger Actuator. +} +``` + +### Conditionals (`if` / `else`) +This is where the "Brain" makes decisions. +```cpp +int lightLevel = analogRead(A0); // Read the LDR (0 to 1023) + +if (lightLevel < 300) { + // It is dark outside + digitalWrite(LED_PIN, HIGH); // Turn street light ON +} else { + // It is bright outside + digitalWrite(LED_PIN, LOW); // Turn street light OFF +} +``` + +--- + +## 5. Practical Engineering Insights + +- **Don't fry your board**: A single GPIO pin on an Arduino can only supply about **20mA to 40mA** of current. This is barely enough to light a standard 5mm LED. If you connect a DC Motor directly to a pin, it will draw too much current and **destroy the microcontroller permanently**. Always use a Motor Driver or a Transistor to control heavy loads! +- **Debouncing**: In the real world, when you press a mechanical button, the metal contacts bounce against each other microscopically. The microcontroller is so fast it reads this as the button being pressed 50 times in a millisecond. In software, you must write "debounce" logic (a small delay) to ignore the microscopic bounces. + +--- + +## 6. Interview-Style Conceptual Questions + +1. **How do you output 2.5V from a microcontroller that only has 0V and 5V logic?** + *Answer: By using Pulse Width Modulation (PWM). You switch the 5V pin ON and OFF very rapidly with a 50% duty cycle, resulting in an average output of 2.5V.* +2. **What happens if your `loop()` function finishes executing the last line of code?** + *Answer: It immediately jumps back up to the first line of the `loop()` function and runs again, continuously.* +3. **If a sensor outputs an analog signal, can you plug it into Pin 2 (a standard Digital pin)?** + *Answer: No. You must plug it into an Analog In pin (like A0) which is connected to the chip's internal ADC (Analog to Digital Converter). Pin 2 would only read HIGH or LOW.* + +--- + +## 7. Practical Project: Smart Street Lights + +City streetlights that remain on at full brightness all night waste massive amounts of electricity. Let's build a smart system that dynamically fades the streetlights ON as the sun sets, and fades them OFF as the sun rises. + +### Working Principle +We will use an LDR (Light Dependent Resistor) to measure ambient sunlight. We will plug this into an Analog pin (A0). Based on the 0-1023 value, we will use a PWM pin (Pin 9) to dynamically fade an LED. + +### Required Components +- 1x Microcontroller (Arduino Uno) +- 1x LDR (Photoresistor) +- 1x 10k Ohm Resistor (for the LDR voltage divider) +- 1x LED & 220 Ohm Resistor (for the LED) + +### Circuit Connection & Breadboard Visual +![LDR Voltage Divider Circuit](/iot/images/m3-ldr-wiring.png) +*Visual Connection Flow: Notice how the LDR and the 10k resistor share a row on the breadboard. The Analog A0 pin connects exactly at this junction to measure the voltage drop!* + +1. **LDR Circuit (Voltage Divider)**: + - Connect one leg of the LDR to 5V. + - Connect the other leg of the LDR to Analog Pin `A0`. + - Also connect that same `A0` leg to a 10k resistor, and connect the other end of the 10k resistor to GND. + *(Why? An LDR changes resistance, not voltage. The microcontroller can only read voltage. This "Voltage Divider" circuit converts the changing resistance into a changing voltage).* +2. **LED Circuit**: + - Connect Pin `9` (which supports PWM, marked with a `~`) to the 220 Ohm resistor. + - Connect the resistor to the long leg (+) of the LED. Connect the short leg (-) to GND. + +### Step-by-Step Code Implementation + +```cpp +// Define Pins +const int ldrPin = A0; // Analog Input pin +const int ledPin = 9; // PWM Output pin (Notice the ~ symbol on your board) + +void setup() { + Serial.begin(9600); + pinMode(ledPin, OUTPUT); + // Note: Analog pins (A0-A5) are inputs by default, pinMode is optional here. +} + +void loop() { + // 1. Read the analog value from LDR (0 to 1023) + int lightLevel = analogRead(ldrPin); + + // 2. Print data for debugging + Serial.print("Sunlight Level: "); + Serial.println(lightLevel); + + // 3. The Logic (Mapping the ranges) + // The map() function translates one range to another. + // Our LDR reads 0 (Dark) to 1023 (Bright). + // PWM requires a value from 0 (Off) to 255 (Full Brightness). + + // We want the opposite effect: If sunlight is Bright (1023), LED is Off (0). + // If sunlight is Dark (0), LED is Full Brightness (255). + int pwmValue = map(lightLevel, 0, 1023, 255, 0); + + // Constrain ensures the value never accidentally goes below 0 or above 255 + pwmValue = constrain(pwmValue, 0, 255); + + // 4. Trigger Actuator + // Using analogWrite() invokes the PWM hardware! + analogWrite(ledPin, pwmValue); + + delay(100); // Small delay for stability +} +``` + +### Code Explanation +- `map(value, fromLow, fromHigh, toLow, toHigh)`: This is an incredibly powerful IoT math function. It scales ranges. If the sunlight is at 512 (50%), the map function automatically calculates that the PWM value should be 127 (50% brightness). +- `analogWrite(pin, value)`: This is how we output PWM. The `value` must be between 0 and 255. `255` means the pin is HIGH 100% of the time. `127` means it is HIGH 50% of the time, resulting in a dim LED. + +### Visual Troubleshooting: PWM Failure +![PWM Troubleshooting](/iot/images/m3-pwm-troubleshoot.png) +*ERROR: The LED is connected to Pin 4. Pin 4 does NOT have a `~` symbol next to it. It cannot do PWM! If you run `analogWrite(4, 127)`, the Arduino will just default to turning the pin completely OFF or completely ON. Always check for the `~` symbol for PWM.* + +### Real-World Application +In a real city, you wouldn't wire a 5V LED. The PWM signal from the microcontroller would go into a heavy-duty **Triac Dimmer Circuit** capable of smoothly dimming 220V AC halogen or high-power LED street lamps. + +--- +**[End of Module 3]** diff --git a/notes/iot/module-04-esp32.md b/notes/iot/module-04-esp32.md new file mode 100644 index 0000000..6db2f93 --- /dev/null +++ b/notes/iot/module-04-esp32.md @@ -0,0 +1,204 @@ +# Module 4: Advanced Controllers & Connectivity (ESP32) + +## 1. Introduction +The Arduino Uno we explored in Module 3 is an excellent tool for learning hardware logic. However, it is missing the one thing required for the "Internet of Things": **The Internet**. + +While you can buy physical Wi-Fi attachments for an Arduino, industry engineers use System-on-a-Chip (SoC) microcontrollers that have Wi-Fi and Bluetooth built directly into the silicon. The undisputed kings of this domain are the **ESP8266 (NodeMCU)** and its big brother, the **ESP32**. + +In this module, we will transition from standalone edge devices to fully connected network nodes. + +--- + +## 2. Learning Objectives +By the end of this module, you will: +- Understand the architecture of modern Wi-Fi microcontrollers (ESP32). +- Learn why power management and "Deep Sleep" are the most critical skills for IoT engineers. +- Understand the basics of Web Servers and IP Addresses in a local network. +- Build a Wi-Fi-enabled Web Server to remotely control physical appliances. + +--- + +## 3. Core Concepts: The ESP32 Architecture + +The ESP32 is a beast compared to the Arduino Uno. Let's look at the specs: +- **Processor**: Dual-Core running at 240 MHz (Arduino has a Single-Core at 16 MHz). +- **Memory**: 520 KB RAM (Arduino has 2 KB). +- **Connectivity**: Built-in 2.4 GHz Wi-Fi and Bluetooth Low Energy (BLE). +- **Hardware Peripherals**: Capacitive touch sensors, built-in Hall Effect (magnetic) sensor. + +### Hardware Spotlight: ESP32 Pinout +![ESP32 Development Board](/iot/images/m4-esp32-pinout.png) +*The 38-pin ESP32 Development Board. Notice the gold Wi-Fi/Bluetooth antenna trace at the top. The pins support advanced features like hardware PWM, multiple ADCs, and capacitive touch.* + +### Why Dual-Core Matters +In an IoT system, the microcontroller has to juggle two very stressful jobs: +1. Running the physics/logic of reading sensors and controlling actuators. +2. Maintaining a constant, stable Wi-Fi connection with a router and parsing heavy network traffic (TCP/IP). +On a single-core chip, if your code takes 2 seconds to calculate a math problem, the Wi-Fi connection will drop because the processor stopped paying attention to the router. With a dual-core ESP32, Core 0 handles the Wi-Fi stack in the background, while Core 1 executes your sensor code. + +--- + +## 4. Hardware Focus: Power Management & Deep Sleep + +Imagine a smart agricultural soil sensor placed in a remote field. It runs on a small Li-Po battery. + +If you leave an ESP32 powered on and connected to Wi-Fi continuously, it draws about **150mA to 240mA** of current. A standard 2000mAh battery will die in **8 hours**. This is a catastrophic failure for an IoT product. + +### The Solution: Deep Sleep +In Deep Sleep mode, the ESP32 shuts down its Wi-Fi, its Bluetooth, and its main Dual-Core processor. It only leaves a microscopic "RTC (Real-Time Clock)" processor running. +In this state, power consumption drops from 240mA to **0.01mA (10 Microamps)**. + +**The Professional IoT Workflow:** +1. Wake up from Deep Sleep. +2. Read the soil moisture sensor (Takes 100 milliseconds). +3. Connect to Wi-Fi (Takes 2 seconds). +4. Send data to the Cloud (Takes 500 milliseconds). +5. Go back to Deep Sleep for 1 hour. + +Using this architecture, that same 2000mAh battery will last **over 3 years** instead of 8 hours! + +--- + +## 5. Network Basics: IP Addresses & Local Servers + +Before we connect to the Cloud in Module 6, we must understand Local Networks (LAN). + +When your ESP32 connects to your home Wi-Fi router, the router assigns it an **IP Address** (e.g., `192.168.1.105`). This is the chip's "phone number" on your network. +If you type that IP address into the web browser of your laptop (which is connected to the same router), your laptop will "call" the ESP32. + +If the ESP32 is running **Web Server** code, it will answer the call and send back a webpage (HTML). This allows you to build a UI to control the hardware! + +--- + +## 6. Interview-Style Conceptual Questions + +1. **You want to build a wearable fitness tracker that lasts for weeks on a coin-cell battery. Why shouldn't you use standard Wi-Fi?** + *Answer: Wi-Fi requires high power to maintain a connection. You should use Bluetooth Low Energy (BLE), which is specifically designed for ultra-low power short-range communication.* +2. **If your ESP32's IP address is `192.168.0.50`, can your friend in another country type that into their browser to turn on your lights?** + *Answer: No. `192.168.x.x` is a Local IP Address, only accessible to devices connected to your specific home router. To allow global access, you need Port Forwarding or a Cloud IoT Platform (covered in later modules).* + +--- + +## 7. Practical Project: Local Web Server for Appliance Control + +We are going to turn the ESP32 into a Web Server. When you visit its IP address on your phone, you will see a webpage with an "ON/OFF" button. Tapping the button will wirelessly toggle an LED or Relay connected to the ESP32. + +### Required Components +- 1x ESP32 Development Board +- 1x LED & 220 Ohm Resistor (or a 5V Relay Module to control a real lamp) +- Home Wi-Fi Network + +### Visual Output: The Phone UI +![Smartphone Web UI](/iot/images/m4-web-ui.png) +*This is the HTML interface we will build. It is served directly from the ESP32's memory to your phone's browser.* + +### Circuit Connection & Breadboard Visual +![ESP32 Relay Wiring](/iot/images/m4-relay-wiring.png) +*Visual Connection Flow: The ESP32's 5V (VIN) pin powers the Relay module. Digital Pin 2 sends a HIGH/LOW signal to the Relay's IN pin, magnetically throwing the high-voltage switch.* + +- Connect the LED's positive leg (or Relay's IN pin) to ESP32 Pin `2`. +- Connect the negative leg (or Relay's GND pin) to ESP32 `GND`. +*(If using a Relay, also connect VCC to the `VIN` or `5V` pin).* + +### Step-by-Step Code Implementation + +> [!WARNING] +> You must install the "ESP32 Board Manager" in your Arduino IDE to compile this code. Replace the SSID and Password with your home Wi-Fi details. + +```cpp +#include +#include + +// Replace with your network credentials +const char* ssid = "YOUR_WIFI_NAME"; +const char* password = "YOUR_WIFI_PASSWORD"; + +// Create a WebServer object listening on standard HTTP port 80 +WebServer server(80); + +const int relayPin = 2; // Pin connected to LED/Relay +bool relayState = false; // Keep track of whether it's ON or OFF + +// HTML and CSS for our Webpage UI +// We store this as a massive string. Notice the CSS styling for a "premium" button. +const char* htmlPage = R"rawliteral( + + + + ESP32 Smart Home + + + + +

    Smart Appliance Control

    +

    Status: UNKNOWN

    + TOGGLE POWER + + +)rawliteral"; + +void setup() { + Serial.begin(115200); // ESP32 uses faster serial by default + pinMode(relayPin, OUTPUT); + digitalWrite(relayPin, LOW); + + // 1. Connect to Wi-Fi + Serial.print("Connecting to Wi-Fi"); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + // Print the IP Address assigned by the router + Serial.println("\nConnected!"); + Serial.print("Visit this IP in your browser: http://"); + Serial.println(WiFi.localIP()); + + // 2. Define Web Server Routes + // What to do when user visits the main page "/" + server.on("/", []() { + server.send(200, "text/html", htmlPage); // Send the HTML string! + }); + + // What to do when user clicks the toggle button and goes to "/toggle" + server.on("/toggle", []() { + relayState = !relayState; // Flip the boolean state + digitalWrite(relayPin, relayState ? HIGH : LOW); // Update the physical pin + + // Redirect the user back to the main page immediately + server.sendHeader("Location", "/"); + server.send(303); + }); + + // 3. Start the server + server.begin(); + Serial.println("HTTP Server Started."); +} + +void loop() { + // 4. Listen for incoming client connections constantly + server.handleClient(); +} +``` + +### Code Explanation & Data Flow +1. **`WiFi.begin()`**: The ESP32's radio turns on and negotiates an encrypted connection with your home router. +2. **`server.on("/", ...)`**: We set up "Routes". When your phone asks for the root directory (`/`), the ESP32 replies with an HTTP `200 OK` code and transmits the `htmlPage` string. Your phone's browser renders this HTML into a visual button. +3. **`server.on("/toggle", ...)`**: When you tap the button on your phone, your browser makes a request to `http://[IP_ADDRESS]/toggle`. The ESP32 receives this, flips the voltage on Pin 2 (turning the Relay on), and tells your phone to redirect back to the home page. + +### Testing & Troubleshooting +- **Cannot connect to Wi-Fi**: The ESP32 *only* supports **2.4 GHz** Wi-Fi networks. It cannot see or connect to 5 GHz networks. Ensure your router is broadcasting a 2.4 GHz band. +- **Serial Monitor shows garbage**: Make sure the baud rate in the bottom right corner of the Serial Monitor is set to `115200` to match the code. + +### Future Improvements +This system works beautifully, but it has a fatal flaw: The web interface is trapped inside the ESP32. If you want 50 devices, you have to type 50 different IP addresses into your browser. In the coming modules, we will decouple the UI from the device, using MQTT to send data to a centralized Cloud Dashboard. + +--- +**[End of Module 4]** diff --git a/notes/iot/module-05-protocols.md b/notes/iot/module-05-protocols.md new file mode 100644 index 0000000..ef48427 --- /dev/null +++ b/notes/iot/module-05-protocols.md @@ -0,0 +1,209 @@ +# Module 5: Communication & Networking Protocols + +## 1. Introduction +In Module 4, we connected an ESP32 to a local Wi-Fi router. While Wi-Fi is great for homes, what if you are building a smart collar to track a cow across a 500-acre farm? There is no Wi-Fi router in the middle of a field. + +To solve different engineering problems, IoT uses many different "languages" and communication methods. In this module, we will explore how devices talk to each other across different ranges, and dive deep into **MQTT**, the undisputed industry standard protocol for IoT data transfer. + +--- + +## 2. Learning Objectives +By the end of this module, you will: +- Understand the trade-offs between Range, Bandwidth, and Power in IoT Networking. +- Compare Short-Range (RFID, Bluetooth, Zigbee) and Long-Range (LoRaWAN, Cellular) protocols. +- Understand why HTTP is terrible for IoT, and why MQTT is better. +- Build an RFID Security System that publishes data via MQTT. + +--- + +## 3. The Iron Triangle of IoT Communication + +When choosing a communication protocol for an IoT project, you must balance three competing factors. You can generally only pick two: +1. **Long Range** (Miles/Kilometers) +2. **High Bandwidth** (Sending lots of data quickly, like video) +3. **Low Power** (Battery lasts for years) + +- **Wi-Fi**: High Bandwidth, Low Range, High Power. (Great for Smart TVs). +- **Cellular (4G/5G)**: High Bandwidth, Long Range, HIGH Power. (Your phone battery dies in a day). +- **LoRaWAN**: Low Bandwidth, Long Range, Low Power. (Perfect for agricultural sensors sending 10 bytes of data per hour). +- **Bluetooth Low Energy (BLE)**: Low Bandwidth, Low Range, Low Power. (Perfect for a smartwatch). + +--- + +## 4. Hardware Protocols: Short vs. Long Range + +### Short-Range Protocols + +#### Hardware Spotlight: RC522 RFID Module +![RC522 RFID Module](/iot/images/m5-rfid-sensor.png) +*The RC522 Module reads the unique serial number hidden inside blank plastic cards or blue keyfobs using near-field radio waves.* + +- **RFID / NFC**: Near-Field Communication. Range is literally a few centimeters. Extremely secure. The "Tag" has no battery; it steals power wirelessly from the "Reader". *Use Case: Keycard door locks, Apple Pay.* +- **Zigbee / Thread**: Mesh networking. Range is ~10-100 meters. Instead of all devices talking to one central router (like Wi-Fi), every smart bulb talks to its neighbor, passing messages along the chain. *Use Case: Philips Hue Smart Bulbs.* + +### Long-Range Protocols (LPWAN) +- **LoRaWAN**: Long Range Wide Area Network. Can transmit data up to 15 kilometers in rural areas. It uses sub-GHz radio frequencies. It is incredibly slow (often taking seconds to send a single sentence of text), but a battery can last 10 years. *Use Case: Forest fire detection sensors.* +- **NB-IoT (Narrowband IoT)**: A cellular standard. It uses existing 4G/5G cell towers but requires very little power. Requires a SIM card and a monthly subscription. *Use Case: Smart city parking meters.* + +--- + +## 5. Application Protocols: HTTP vs. MQTT + +Once the physical radio waves reach the internet, how is the data formatted? + +### Why HTTP fails in IoT +In Module 4, we used HTTP (Hypertext Transfer Protocol). HTTP is a "Request/Response" protocol. Your phone *asks* for the webpage, and the server *responds*. +- **The Problem**: It is extremely "heavy". Sending a single number like `25` (the temperature) using HTTP requires sending hundreds of bytes of "Header" text (telling the server what browser you use, what language you speak, etc.). For a battery-powered sensor on a slow LoRa connection, this is a massive waste of energy. + +### The Solution: MQTT (Message Queuing Telemetry Transport) +MQTT was invented in 1999 for monitoring oil pipelines over satellite links. It is a "Publish/Subscribe" protocol. It is incredibly lightweight; the header is only 2 bytes! + +#### How MQTT Works (Pub/Sub) +Instead of devices talking directly to each other, everything talks to a central server called a **Broker**. + +1. **Topics**: Data is organized into folders called topics, like `home/livingroom/temperature`. +2. **Publishing**: A temperature sensor *Publishes* the number `25` to the topic `home/livingroom/temperature`. +3. **Subscribing**: Your phone app *Subscribes* to that topic. +4. **The Magic**: The moment the sensor publishes the data, the Broker instantly pushes that data to *any* device subscribed to the topic. The phone didn't have to ask for it! + +### Visual Flow: MQTT Architecture +![MQTT Pub-Sub Flow](/iot/images/m5-mqtt-flow.png) +*Visual Connection Flow: Notice how the Publisher (ESP32) and the Subscriber (Phone) NEVER talk directly to each other. They only talk to the central Broker.* + +--- + +## 6. Interview-Style Conceptual Questions + +1. **You need to stream a live video feed from a drone 5 miles away. Should you use LoRaWAN?** + *Answer: No. LoRaWAN has extremely low bandwidth (kilobits per second). Video requires megabits per second. You would need Cellular (4G/5G) or a dedicated high-frequency radio link.* +2. **In MQTT, what happens if a sensor publishes data to a topic, but the phone app is turned off?** + *Answer: The Broker receives the data, realizes no one is currently subscribed to listen, and discards the data. (Unless you configure "Retained Messages", which tells the Broker to save the very last message sent for the next person who connects).* + +--- + +## 7. Practical Project: RFID Security System & MQTT Publisher + +We will build an industrial-style security system. Employees will scan an RFID keycard. The microcontroller will check if the card is authorized. If it is, it will trigger a relay (to unlock a door) AND publish a message via Wi-Fi/MQTT to a global cloud broker so the security team can monitor access live. + +### Required Components +- 1x ESP32 Development Board +- 1x RC522 RFID Reader Module & Blank Cards/Tags +- 1x LED (Simulating a Door Lock Relay) + +### Circuit Connection (SPI Protocol) & Breadboard Visual +![SPI RFID Wiring](/iot/images/m5-spi-wiring.png) +*Visual Connection Flow: SPI is a high-speed synchronous protocol requiring 4 data lines (MISO, MOSI, SCK, CS). The RC522 operates strictly at 3.3V. Connecting its VCC to 5V will instantly destroy it.* + +The RC522 uses the **SPI** (Serial Peripheral Interface) protocol, which requires 4 specific pins to communicate very fast. +- **SDA (SS)** to ESP32 Pin `5` +- **SCK (Clock)** to ESP32 Pin `18` +- **MOSI** to ESP32 Pin `23` +- **MISO** to ESP32 Pin `19` +- **GND** to `GND`, **3.3V** to `3.3V` (DO NOT connect to 5V, the RC522 will fry). + +### Step-by-Step Code Implementation + +> [!TIP] +> You will need to install two libraries in the Arduino IDE: `MFRC522` (for the RFID reader) and `PubSubClient` (for MQTT). + +```cpp +#include +#include +#include +#include + +// Wi-Fi & MQTT Settings +const char* ssid = "YOUR_WIFI"; +const char* password = "YOUR_PASSWORD"; +const char* mqtt_server = "broker.hivemq.com"; // A free public MQTT broker + +WiFiClient espClient; +PubSubClient client(espClient); + +// RFID Settings +#define SS_PIN 5 +#define RST_PIN 22 +MFRC522 rfid(SS_PIN, RST_PIN); + +// The authorized card ID (You will find yours in the serial monitor) +String authorizedCard = "A1 B2 C3 D4"; +const int lockRelayPin = 2; + +void setup() { + Serial.begin(115200); + pinMode(lockRelayPin, OUTPUT); + + // Initialize SPI bus and RFID reader + SPI.begin(); + rfid.PCD_Init(); + + // Connect Wi-Fi + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } + Serial.println("\nWi-Fi Connected"); + + // Setup MQTT + client.setServer(mqtt_server, 1883); +} + +void reconnectMQTT() { + while (!client.connected()) { + Serial.print("Attempting MQTT connection..."); + // Create a random client ID + String clientId = "ESP32Door-" + String(random(0xffff), HEX); + if (client.connect(clientId.c_str())) { + Serial.println("connected"); + } else { + delay(5000); + } + } +} + +void loop() { + if (!client.connected()) { reconnectMQTT(); } + client.loop(); // Keep MQTT connection alive + + // Check if a new RFID card is present + if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) { + return; + } + + // Read the Card UID (Unique ID) + String scannedCard = ""; + for (byte i = 0; i < rfid.uid.size; i++) { + scannedCard += String(rfid.uid.uidByte[i] < 0x10 ? " 0" : " "); + scannedCard += String(rfid.uid.uidByte[i], HEX); + } + scannedCard.trim(); + scannedCard.toUpperCase(); + + Serial.println("Card Scanned: " + scannedCard); + + // Logic: Is it the authorized card? + if (scannedCard == authorizedCard) { + Serial.println("Access Granted!"); + digitalWrite(lockRelayPin, HIGH); // Unlock Door + + // Publish to the Cloud via MQTT! + client.publish("factory/door1/logs", "ACCESS GRANTED - User ID: A1B2C3D4"); + + delay(3000); // Keep unlocked for 3 seconds + digitalWrite(lockRelayPin, LOW); // Lock Door + } else { + Serial.println("Access Denied!"); + client.publish("factory/door1/logs", "ACCESS DENIED - Unknown Card"); + } + + // Stop reading this specific card to prevent spamming + rfid.PICC_HaltA(); +} +``` + +### Code Explanation & Data Flow +1. **The Physical Layer**: When a plastic keycard nears the RC522 reader, the magnetic field powers a microchip inside the card. The chip broadcasts its Unique ID (e.g., `A1 B2 C3 D4`) via radio waves. +2. **The Local Controller**: The ESP32 receives the ID via SPI. It checks an `if` statement to see if the ID matches the database. +3. **The Application Layer (MQTT)**: If authorized, the ESP32 acts as an MQTT Publisher. It connects to `broker.hivemq.com` (a global server located in Germany) and publishes a text string to the topic `factory/door1/logs`. +4. **The Result**: Anyone in the world with an MQTT app on their phone subscribed to `factory/door1/logs` will instantly receive a push notification that the door was opened! + +--- +**[End of Module 5]** diff --git a/notes/iot/module-06-cloud.md b/notes/iot/module-06-cloud.md new file mode 100644 index 0000000..8a77347 --- /dev/null +++ b/notes/iot/module-06-cloud.md @@ -0,0 +1,205 @@ +# Module 6: Cloud Integration & IoT Platforms + +## 1. Introduction +In Module 5, we learned how to send data from an ESP32 to an MQTT Broker. However, an MQTT Broker does not *save* the data. It just acts as a postman, delivering messages to active listeners. + +If you are a farmer trying to figure out why your crop died last month, a live MQTT stream won't help you. You need **Historical Data**. You need databases, graphs, and cloud platforms. + +In this module, we move from the Edge (Microcontrollers) to the Cloud (Servers). We will learn how to permanently store our sensor data in databases and visualize it using industry-standard IoT platforms. + +--- + +## 2. Learning Objectives +By the end of this module, you will: +- Understand the role of an IoT Cloud Platform (ThingSpeak, AWS IoT, Blynk). +- Master REST APIs and understand the difference between HTTP GET and POST. +- Understand JSON (JavaScript Object Notation), the universal language of cloud data. +- Build a complete, cloud-connected Weather Monitoring Station. + +--- + +## 3. Core Concepts: IoT Platforms & Databases + +Building a secure database from scratch (like SQL or MongoDB) and exposing it securely to the internet is difficult. To speed up development, engineers use **IoT Cloud Platforms**. + +These platforms provide: +1. **Data Ingestion**: Secure APIs to receive data from millions of devices simultaneously. +2. **Time-Series Databases**: Databases specifically optimized to store timestamped sensor data (e.g., "Temp was 25C at 10:04 AM", "Temp was 26C at 10:05 AM"). +3. **Visualization**: Built-in drag-and-drop graphs and gauges. + +### Popular IoT Platforms +- **ThingSpeak (by MathWorks)**: Highly academic and engineering-focused. Excellent for data logging and math analysis (integrates with MATLAB). +- **Blynk**: Highly consumer-focused. Excellent for creating mobile apps to control smart home devices visually. +- **AWS IoT Core / Azure IoT Hub**: Enterprise-focused. Used by massive companies (like Tesla or Philips) to manage millions of devices. Incredibly powerful but highly complex to set up. + +--- + +## 4. Software Focus: APIs & JSON + +How does an ESP32 actually hand the data over to the Cloud database? It uses an **API** (Application Programming Interface). + +### HTTP GET vs. POST +When interacting with a REST API, you use standard HTTP methods: +- **GET Request**: "Hey Cloud, what is the current status of the living room light?" (Used to *read* data). +- **POST Request**: "Hey Cloud, save this new temperature reading into your database." (Used to *write* data). + +### JSON (JavaScript Object Notation) +When you POST data to a modern cloud, you don't just send the raw number `25`. The cloud won't know what `25` means. You send a structured text format called JSON. + +An ESP32 formatting temperature and humidity into JSON looks like this: +```json +{ + "device_id": "ESP_Station_1", + "temperature": 25.4, + "humidity": 60, + "timestamp": "2023-10-25T14:30:00Z" +} +``` + +--- + +## 5. Practical Engineering Insights + +- **Data Hoarding**: An ESP32 reading a sensor every second will generate **86,400 database rows** per day. For 1,000 devices, that's 86 million rows a day! Your cloud server will crash, and your AWS bill will bankrupt you. **Rule of Thumb**: Only send data to the cloud when the data *changes significantly*, or aggregate it locally and send an average every 15 minutes. +- **API Keys**: When you create a ThingSpeak account, you get an API Key (a long string of random letters). This is your password. If you upload your ESP32 code to GitHub with the API Key inside it, hackers will steal it and spam your database. Always keep API keys secret! + +--- + +## 6. Interview-Style Conceptual Questions + +1. **Why use a Time-Series Database (like InfluxDB) instead of a standard SQL database for IoT?** + *Answer: IoT data is exclusively timestamp-based. Time-series databases are optimized to ingest massive amounts of timestamped data very quickly and query it efficiently (e.g., "Give me the average temperature over the last 30 days").* +2. **What is the difference between an API and a User Interface (UI)?** + *Answer: A UI is built for humans to look at and click (like a website). An API is built for machines (like an ESP32) to send raw data to a server in a structured format.* + +--- + +## 7. Practical Project: Global Weather Monitoring Station + +We will build a professional weather station. An ESP32 will read temperature and humidity from a DHT11 sensor. It will then make an HTTP API call to **ThingSpeak**, permanently storing the data in a cloud database and plotting it on a live graph accessible from anywhere in the world. + +### Hardware Spotlight: DHT11 Sensor +![DHT11 Sensor](/iot/images/m6-dht11-sensor.png) +*The DHT11 is a basic, ultra-low-cost digital temperature and humidity sensor. It uses a capacitive humidity sensor and a thermistor to measure the surrounding air, and spits out a digital signal on the data pin.* + +### Required Components +- 1x ESP32 Development Board +- 1x DHT11 Temperature & Humidity Sensor +- 1x ThingSpeak Account (Free) + +### Cloud Setup & Visual UI (ThingSpeak) +![ThingSpeak Dashboard](/iot/images/m6-thingspeak-ui.png) +*Visual Output: This is the ThingSpeak dashboard we are building. The ESP32 will push data to the Cloud, and ThingSpeak will automatically render these historical line graphs.* + +1. Go to thingspeak.com and create a free account. +2. Click **New Channel**. Name it "Weather Station". +3. Enable **Field 1** (name it "Temperature") and **Field 2** (name it "Humidity"). +4. Save the channel. Go to the "API Keys" tab and copy your **Write API Key**. + +### Circuit Connection +- **DHT11 VCC** to ESP32 `3.3V` +- **DHT11 GND** to ESP32 `GND` +- **DHT11 DATA** to ESP32 Pin `4` + +### Step-by-Step Code Implementation + +> [!TIP] +> Install the `DHT sensor library` by Adafruit in your Arduino IDE. + +```cpp +#include +#include +#include + +// Wi-Fi Credentials +const char* ssid = "YOUR_WIFI"; +const char* password = "YOUR_PASSWORD"; + +// ThingSpeak API Settings +String apiKey = "YOUR_THINGSPEAK_WRITE_API_KEY"; +const char* serverName = "http://api.thingspeak.com/update"; + +// Sensor Settings +#define DHTPIN 4 +#define DHTTYPE DHT11 +DHT dht(DHTPIN, DHTTYPE); + +// Timer variables (We only want to send data every 15 seconds) +unsigned long lastTime = 0; +unsigned long timerDelay = 15000; + +void setup() { + Serial.begin(115200); + dht.begin(); + + WiFi.begin(ssid, password); + Serial.print("Connecting to WiFi"); + while(WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } + Serial.println("\nConnected to WiFi network with IP Address: "); + Serial.println(WiFi.localIP()); +} + +void loop() { + // Edge Computing Rule: Don't spam the cloud. Only run every 15 seconds. + if ((millis() - lastTime) > timerDelay) { + + // 1. Read Sensor Data + float temp = dht.readTemperature(); + float hum = dht.readHumidity(); + + // Check if the reading failed + if (isnan(temp) || isnan(hum)) { + Serial.println("Failed to read from DHT sensor!"); + return; + } + + Serial.println("Temp: " + String(temp) + "°C | Humidity: " + String(hum) + "%"); + + // 2. Check WiFi connection status + if(WiFi.status()== WL_CONNECTED){ + WiFiClient client; + HTTPClient http; + + // 3. Construct the API URL + // ThingSpeak uses a simple URL parameter structure for GET requests: + // http://api.thingspeak.com/update?api_key=XYZ&field1=25&field2=60 + String serverPath = serverName + "?api_key=" + apiKey + "&field1=" + String(temp) + "&field2=" + String(hum); + + // 4. Make the HTTP Request + http.begin(client, serverPath.c_str()); + + // Send HTTP GET request + int httpResponseCode = http.GET(); + + if (httpResponseCode > 0) { + Serial.print("HTTP Response code: "); + Serial.println(httpResponseCode); // 200 means OK! + } + else { + Serial.print("Error code: "); + Serial.println(httpResponseCode); + } + + // Free resources + http.end(); + } + else { + Serial.println("WiFi Disconnected"); + } + + // Reset timer + lastTime = millis(); + } +} +``` + +### Code Explanation & Data Flow +1. **Local Timing**: We use `millis()`, which returns the number of milliseconds since the board booted up. By checking `millis() - lastTime`, we create a non-blocking 15-second timer. This allows the ESP32 to theoretically do other things (like checking a motion sensor) while waiting to send data to the cloud. +2. **String Construction**: We dynamically build a URL string by appending our sensor variables (`temp` and `hum`) onto the ThingSpeak API URL. +3. **HTTP GET**: `http.GET()` executes the network request. The ESP32 reaches out to the ThingSpeak server in America, hands over the URL string, and waits for a response. A response code of `200` means the server successfully saved the data! + +### Result +Go to your ThingSpeak Dashboard. You will see two beautiful line graphs actively plotting your room's temperature and humidity over time. You can view this webpage from anywhere on Earth, proving your device is fully integrated into the IoT ecosystem! + +--- +**[End of Module 6]** diff --git a/notes/iot/module-07-analytics.md b/notes/iot/module-07-analytics.md new file mode 100644 index 0000000..ad1cf88 --- /dev/null +++ b/notes/iot/module-07-analytics.md @@ -0,0 +1,177 @@ +# Module 7: IoT Data Analytics & Machine Learning + +## 1. Introduction +In Module 6, we successfully pushed our sensor data into a cloud database. We now have 10,000 temperature and humidity data points. But data sitting in a database is useless on its own. + +**Data only has value when it is converted into Actionable Intelligence.** + +This is where IoT Data Analytics comes in. How do we take massive datasets and use them to save a company money, prevent a machine from breaking, or optimize crop yields? In this module, we will explore Rule Engines, Edge Analytics, and the intersection of IoT and Machine Learning (ML). + +--- + +## 2. Learning Objectives +By the end of this module, you will: +- Understand the difference between descriptive, predictive, and prescriptive analytics. +- Learn how Cloud Rule Engines automate complex tasks without human intervention. +- Understand how Machine Learning is applied to IoT (Anomaly Detection). +- Build a Smart Irrigation System that uses rule-based analytics to optimize water usage. + +--- + +## 3. Core Concepts: The Analytics Ladder + +When a business deploys an IoT system, they generally progress through four stages of analytics maturity: + +### 1. Descriptive Analytics (What happened?) +- *Example*: A dashboard showing the soil moisture was 20% yesterday. +- This is basic reporting. It is useful, but a human still has to look at the graph and decide what it means. + +### 2. Diagnostic Analytics (Why did it happen?) +- *Example*: Cross-referencing the soil moisture graph with the cloud weather API graph to realize the soil dried out because the temperature spiked to 40°C. + +### 3. Predictive Analytics (What will happen?) +- *Example*: Using Machine Learning algorithms on the last 5 years of soil data to predict that, based on current trends, the soil will reach critical dryness in exactly 48 hours. + +### 4. Prescriptive Analytics (What should we do?) +- *Example*: The AI not only predicts the dryness but automatically turns on the irrigation system for exactly 14 minutes to restore optimal moisture, while sending a report to the farmer. + +--- + +## 4. Software Focus: Cloud Rule Engines + +You do not always need complex AI. Most industrial IoT automation is powered by **Rule Engines**. + +A Rule Engine is a cloud-based software component that constantly evaluates incoming telemetry against a set of `IF / THEN` statements. + +**Example of a Complex Cloud Rule:** +> `IF` (Moisture < 30%) `AND` (Weather Forecast == "No Rain") `AND` (Time of Day == "Night") `THEN` (Trigger Relay_1) `AND` (Send SMS Alert). + +*Why do this in the Cloud instead of on the ESP32?* +Because the ESP32 doesn't know the Weather Forecast. It only knows its local sensor. The Cloud integrates multiple data sources (Sensor data + Web APIs + Timezones) to make highly intelligent decisions. + +--- + +## 5. Machine Learning at the Edge (TinyML) + +Sending thousands of data points to the cloud per second for AI analysis is expensive. A massive trend in IoT is **TinyML**—running Machine Learning models directly on the microcontroller. + +- **Anomaly Detection**: Imagine a vibration sensor on a factory motor. Instead of streaming raw vibration data to AWS, you train a neural network to recognize the "normal" vibration pattern. You upload this trained model to the ESP32. +- The ESP32 processes the vibrations locally (Edge Computing). It only sends a message to the cloud if the ML model detects an *anomaly* (a weird vibration that usually means the motor is breaking). + +--- + +## 6. Interview-Style Conceptual Questions + +1. **What is the main advantage of Edge Analytics (TinyML) over Cloud Analytics?** + *Answer: Reduced latency (decisions are made instantly without internet delays), significantly reduced bandwidth costs, and improved privacy (raw data never leaves the device).* +2. **If a Cloud Rule Engine triggers an actuator, but the Wi-Fi goes down, what happens?** + *Answer: The system fails. This is a critical design flaw. Life-critical safety rules (like shutting down an overheating boiler) MUST be hardcoded onto the local microcontroller. The Cloud should only handle optimization rules, not safety rules.* + +--- + +## 7. Practical Project: Smart Agriculture & Irrigation Analytics + +We will build a Smart Irrigation node. An ESP32 will read soil moisture. Instead of just "turning on a pump when it's dry," we will implement a local rule engine that evaluates thresholds, prevents overwatering by implementing "soak delays", and pushes analytical data to the serial monitor/cloud. + +### Hardware Spotlight: Soil Moisture Sensor +![Placeholder: Soil Moisture Sensor](/iot/images/placeholder-m7-sensor.png) +*A Capacitive Soil Moisture Sensor. Unlike cheap resistive sensors that corrode in water within weeks, capacitive sensors measure changes in capacitance to determine moisture, lasting for years in harsh soil.* + +### Required Components +- 1x ESP32 Development Board +- 1x Capacitive Soil Moisture Sensor (Analog) +- 1x 5V Relay Module (to control a water pump) +- 1x Mini Submersible Water Pump (Optional, for real testing) + +### Circuit Connection & Breadboard Visual +![Placeholder: Smart Irrigation Wiring](/iot/images/placeholder-m7-wiring.png) +*Visual Connection Flow: The Moisture sensor outputs an analog voltage to ESP32 Pin 34. Based on the local Rule Engine, the ESP32 sends a digital HIGH signal to Pin 2, triggering the Relay to power the high-current water pump.* + +- **Moisture Sensor**: VCC to `3.3V`, GND to `GND`, AOUT to ESP32 Analog Pin `34`. +- **Relay**: IN to ESP32 Digital Pin `2`, VCC to `5V`, GND to `GND`. + +### Step-by-Step Code Implementation + +```cpp +// Define Pins +const int moisturePin = 34; +const int pumpRelayPin = 2; + +// Analytics Thresholds (Derived from historical data) +const int DRY_THRESHOLD = 30; // Below 30% is too dry +const int OPTIMAL_MOISTURE = 65; // Stop watering at 65% + +// State tracking for our local rule engine +bool isWatering = false; +unsigned long wateringStartTime = 0; +const unsigned long MAX_WATERING_TIME = 10000; // 10 seconds max to prevent flooding + +void setup() { + Serial.begin(115200); + pinMode(pumpRelayPin, OUTPUT); + digitalWrite(pumpRelayPin, LOW); // Ensure pump is OFF + + Serial.println("Smart Irrigation Analytics System Booting..."); +} + +void loop() { + // 1. Data Collection (Read Analog ADC) + int rawADC = analogRead(moisturePin); // ESP32 ADC is 12-bit (0 to 4095) + + // 2. Data Transformation (Convert raw voltage to Percentage) + // Note: Capacitive sensors read HIGHER values when DRY. + // You must calibrate these numbers (2000 = Wet water, 4000 = Bone dry air) + int moisturePercent = map(rawADC, 4000, 2000, 0, 100); + moisturePercent = constrain(moisturePercent, 0, 100); + + // 3. Descriptive Analytics Output + Serial.print("Current Moisture Level: "); + Serial.print(moisturePercent); + Serial.println("%"); + + // 4. Prescriptive Analytics (The Local Rule Engine) + + // RULE 1: If soil is dry, and we aren't already watering, start watering. + if (moisturePercent < DRY_THRESHOLD && !isWatering) { + Serial.println("ACTION: Soil critically dry. Triggering Irrigation."); + digitalWrite(pumpRelayPin, HIGH); // Turn ON pump + isWatering = true; + wateringStartTime = millis(); + } + + // RULE 2: If we are watering, check if we reached optimal levels + if (isWatering) { + if (moisturePercent >= OPTIMAL_MOISTURE) { + Serial.println("ACTION: Optimal moisture reached. Stopping Irrigation."); + digitalWrite(pumpRelayPin, LOW); // Turn OFF pump + isWatering = false; + } + + // RULE 3: Failsafe / Anomaly Detection + // If the pump has been running for 10 seconds and the soil is STILL dry, + // the water tank is probably empty, or the sensor is broken! + if (millis() - wateringStartTime > MAX_WATERING_TIME) { + Serial.println("ANOMALY DETECTED: Pump timeout! Possible empty tank or broken pipe."); + digitalWrite(pumpRelayPin, LOW); // EMERGENCY SHUTOFF + isWatering = false; + // In a full system, you would send an MQTT Alert to the farmer here! + delay(60000); // Wait a minute before trying again + } + } + + // Delay before next reading (Edge computing optimization) + delay(2000); +} +``` + +### Code Explanation & Data Flow +1. **Calibration**: The `map()` function is used to transform raw electrical ADC values (e.g., `3450`) into a human-readable percentage (`42%`). This is the first step of data analytics. +2. **The Local Rule Engine**: Instead of simple `IF/ELSE`, we track the *state* of the system using `isWatering`. This allows us to apply complex logic across multiple loops. +3. **Anomaly Failsafe**: This is critical engineering. Pumps burn out if they run without water. By tracking `wateringStartTime`, the microcontroller actively predicts a hardware failure (an empty tank) if the moisture doesn't rise within 10 seconds, safely shutting down the system and averting disaster. + +### Visual Troubleshooting: The Dry Pump Failsafe +![Placeholder: Pump Timeout Error](/iot/images/placeholder-m7-troubleshooting.png) +*If the soil moisture does not rise after 10 seconds of pumping, the rule engine detects an anomaly (e.g. empty water tank) and executes an EMERGENCY SHUTOFF to prevent the pump motor from burning out.* + +--- +**[End of Module 7]** diff --git a/notes/iot/module-08-smart-automation.md b/notes/iot/module-08-smart-automation.md new file mode 100644 index 0000000..529cf94 --- /dev/null +++ b/notes/iot/module-08-smart-automation.md @@ -0,0 +1,184 @@ +# Module 8: Smart Automation & Ecosystems + +## 1. Introduction +Up until now, we have built isolated devices: A weather station, a smart dustbin, an RFID lock. But true IoT power is unleashed when devices start talking to *each other* without human or cloud intervention. This is called **Machine-to-Machine (M2M) Communication**. + +In this module, we will explore how to build entire ecosystems of devices. We will look at how modern Smart Homes operate using M2M architectures, and how protocols like Matter and Thread are solving the "fragmentation" problem in the industry. + +--- + +## 2. Learning Objectives +By the end of this module, you will: +- Understand M2M (Machine-to-Machine) communication and Distributed Logic. +- Understand the Smart Home ecosystem fragmentation problem and how "Matter" solves it. +- Learn how to design a system with **Local Fallback** (surviving internet outages). +- Build a multi-sensor Smart Home node for Gas Leakage Detection and Automation. + +--- + +## 3. Core Concepts: M2M & Distributed Architecture + +### The Problem with Centralized Cloud +In a standard IoT architecture, if an ESP32 motion sensor detects movement, it sends a message to the AWS Cloud. The Cloud processes it, and sends a command back down to a different ESP32 controlling a smart lightbulb. +- **Flaw 1 (Latency)**: It takes 200-500ms. You feel a noticeable lag between walking into the room and the lights turning on. +- **Flaw 2 (Reliability)**: If your internet drops, your smart home breaks. You are sitting in the dark. + +### The Solution: Distributed M2M +In M2M, devices talk directly to each other on the local network. +The Motion Sensor broadcasts a local message: "I detected motion." +The Smart Bulb is programmed to listen for that specific message and turns itself on instantly (Latency < 10ms). The Cloud is only notified *after the fact* for data logging. + +--- + +## 4. Industry Standards: Matter & Thread + +If you buy a Philips Smart Bulb and a Samsung Smart TV, they cannot talk to each other. They use different languages (Zigbee vs. Wi-Fi) and different apps. This fragmentation stalled the IoT industry for years. + +To fix this, Apple, Google, Amazon, and Samsung created **Matter**. + +- **Matter**: An application layer protocol. It is a universal translator. If a device has the Matter logo, it guarantees it can talk to any other Matter device locally, regardless of the brand. +- **Thread**: The underlying radio protocol for Matter. It creates a self-healing mesh network. If one smart plug in the kitchen breaks, the other devices automatically route their messages around it to reach the hub. + +--- + +## 5. Practical Engineering Insights: Failsafes + +When building automation, always design for the worst-case scenario. + +**The "Local Fallback" Rule:** +If you build a Smart Gas Leak detector, its primary job is to send a push notification to your phone. But if the house is on fire, the Wi-Fi router might melt before the notification is sent. +**Engineering Solution**: The gas detector must have a physical, ear-piercing buzzer hardwired into its circuit. It must sound the alarm *locally* before attempting to connect to the cloud. Never rely entirely on the cloud for life-safety systems. + +--- + +## 6. Interview-Style Conceptual Questions + +1. **What is the difference between IoT and M2M?** + *Answer: IoT implies that devices connect to the Internet/Cloud to share data and be analyzed. M2M (Machine-to-Machine) simply means devices communicating directly with each other (e.g., via Bluetooth or local Wi-Fi) without necessarily touching the global internet.* +2. **Why is a Mesh Network (like Thread/Zigbee) better for a Smart Home than standard Wi-Fi?** + *Answer: Standard Wi-Fi has a central point of failure (the router) and limited range. In a Mesh Network, every smart plug and lightbulb acts as a repeater, extending the range throughout the entire house and healing the network if one node dies.* + +--- + +## 7. Practical Project: Smart Home Automation & Gas Detection + +We will build a comprehensive Smart Home Node. It will perform two tasks concurrently: +1. **Home Automation**: Control a room's lights via Wi-Fi commands. +2. **Life Safety**: Continuously monitor for combustible gas (LPG/Smoke) using an MQ-2 sensor. If gas is detected, it will trigger a local alarm (Buzzer) instantly, and attempt to send an M2M/Cloud alert. + +### Hardware Spotlight: MQ-2 Gas Sensor +![Placeholder: MQ-2 Gas Sensor](/iot/images/placeholder-m8-sensor.png) +*The MQ-2 Smoke and Combustible Gas sensor. It has a built-in heater coil that must warm up before taking accurate readings. It usually features both an Analog Out (AOUT) for precise gas concentration, and a Digital Out (DOUT) with a potentiometer for simple HIGH/LOW triggers.* + +### Required Components +- 1x ESP32 Development Board +- 1x MQ-2 Gas/Smoke Sensor (Analog) +- 1x Active Buzzer +- 1x LED or Relay (for Room Light) + +### Circuit Connection & Breadboard Visual +![Placeholder: Smart Home Multi-Node Wiring](/iot/images/placeholder-m8-wiring.png) +*Visual Connection Flow: Notice the power requirements. The MQ-2 sensor draws significant current for its heater coil. It is highly recommended to power it from an external 5V power supply rather than directly from the ESP32's 5V pin, ensuring you connect the Grounds together.* + +- **MQ-2 Sensor**: VCC to `5V`, GND to `GND`, AOUT to ESP32 Analog Pin `35`. *(Note: MQ sensors draw a lot of heater current. If the ESP32 crashes, power the sensor from a separate 5V supply sharing a common ground).* +- **Buzzer**: Positive to Digital Pin `18`, Negative to `GND`. +- **Light Relay**: IN to Digital Pin `19`, Negative to `GND`. + +### Step-by-Step Code Implementation + +```cpp +#include +#include + +// Wi-Fi Configuration +const char* ssid = "YOUR_WIFI"; +const char* password = "YOUR_WIFI_PASSWORD"; +WebServer server(80); + +// Pin Definitions +const int gasSensorPin = 35; +const int buzzerPin = 18; +const int lightRelayPin = 19; + +// Gas Threshold (Calibrate this based on your room) +const int DANGER_THRESHOLD = 2000; // ESP32 ADC goes up to 4095 + +// State variable +bool isLightOn = false; + +void setup() { + Serial.begin(115200); + pinMode(buzzerPin, OUTPUT); + pinMode(lightRelayPin, OUTPUT); + + // Start with lights and buzzer off + digitalWrite(buzzerPin, LOW); + digitalWrite(lightRelayPin, LOW); + + // Connect to Wi-Fi + WiFi.begin(ssid, password); + Serial.print("Connecting to Wi-Fi..."); + while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } + Serial.println("\nIP Address: "); + Serial.println(WiFi.localIP()); + + // Setup Web Server Routes for Home Automation + server.on("/light/on", []() { + digitalWrite(lightRelayPin, HIGH); + isLightOn = true; + server.send(200, "text/plain", "Light is ON"); + }); + + server.on("/light/off", []() { + digitalWrite(lightRelayPin, LOW); + isLightOn = false; + server.send(200, "text/plain", "Light is OFF"); + }); + + server.begin(); +} + +void loop() { + // 1. Handle incoming HTTP requests for Home Automation + server.handleClient(); + + // 2. Life Safety Critical Loop (Runs constantly, unaffected by Wi-Fi delays) + int gasLevel = analogRead(gasSensorPin); + + if (gasLevel > DANGER_THRESHOLD) { + Serial.println("CRITICAL: GAS DETECTED! Level: " + String(gasLevel)); + + // LOCAL FALLBACK: Sound the alarm instantly! + digitalWrite(buzzerPin, HIGH); + + // SAFETY AUTOMATION: If gas is detected, turn OFF the lights/relays + // to prevent electrical sparks from causing an explosion. + if (isLightOn) { + digitalWrite(lightRelayPin, LOW); + isLightOn = false; + } + + // In a real M2M system, we would broadcast a UDP packet here + // to tell ALL other devices in the house to shut down. + + delay(500); // Pulse the buzzer + digitalWrite(buzzerPin, LOW); + delay(500); + } else { + // Normal operation, ensure buzzer is off + digitalWrite(buzzerPin, LOW); + } +} +``` + +### Code Explanation & System Architecture +This code beautifully demonstrates a **Hybrid Architecture**: +1. **The Automation Thread (`server.handleClient`)**: The ESP32 acts as a server. Your phone app (or a central Home Assistant hub) can send `GET /light/on` commands to control the room. This relies on Wi-Fi. +2. **The Safety Thread (`analogRead(gasSensorPin)`)**: This runs sequentially in the loop, completely independent of the Wi-Fi. If the Wi-Fi router dies, you cannot turn on the light from your phone. BUT, if a gas leak occurs, the local `if(gasLevel > DANGER)` logic still executes flawlessly, sounding the buzzer and averting a tragedy. This is robust engineering. + +### Visual Troubleshooting: False Alarms vs Warmup +![Placeholder: Sensor Heating Up](/iot/images/placeholder-m8-troubleshooting.png) +*When you first turn on the MQ-2 sensor, its analog output will spike wildly for the first 30 seconds as the internal coil heats up. Do not trigger alarms during this warmup phase!* + +--- +**[End of Module 8]** diff --git a/notes/iot/module-09-iiot-security.md b/notes/iot/module-09-iiot-security.md new file mode 100644 index 0000000..7aa4c96 --- /dev/null +++ b/notes/iot/module-09-iiot-security.md @@ -0,0 +1,190 @@ +# Module 9: Industrial IoT (IIoT) & Security + +## 1. Introduction +A Smart Home lightbulb failing to turn on is an annoyance. A smart valve failing to close in a chemical plant is a catastrophe. + +When IoT moves from the living room to the factory floor, it becomes **Industrial IoT (IIoT)**. In this arena, reliability, durability, and security are no longer optional—they are a matter of life and death. In this module, we will explore how engineers design IIoT systems to survive harsh environments and malicious hackers. + +--- + +## 2. Learning Objectives +By the end of this module, you will: +- Understand the difference between Consumer IoT and Industrial IoT (IIoT). +- Understand how legacy SCADA systems integrate with modern IIoT. +- Learn about the biggest vulnerabilities in IoT (like the Mirai Botnet). +- Understand Encryption (TLS/SSL) and Over-The-Air (OTA) updates. +- Build a secure Industrial Machine Temperature Monitor. + +--- + +## 3. Core Concepts: IIoT vs. Consumer IoT + +### 1. Hardware Durability +- **Consumer IoT**: Uses standard microcontrollers and cheap DHT11 temperature sensors in plastic casing. Operating temp: 0°C to 50°C. +- **IIoT**: Uses ruggedized PLC (Programmable Logic Controller) enclosures, IP68 waterproofing, and industrial Thermocouples capable of measuring molten metal up to 1000°C. + +### 2. Communication Reliability +- **Consumer IoT**: Wi-Fi drops constantly. It's acceptable for a home device to try reconnecting for 5 minutes. +- **IIoT**: Uses deterministic networks (like wired Ethernet, RS485, or private 5G) where message delivery is guaranteed within milliseconds. + +### 3. SCADA Systems +Before IIoT, factories used SCADA (Supervisory Control and Data Acquisition) systems. These were massive, wired, closed-loop systems that did not connect to the internet. Modern IIoT acts as an "overlay," taking data from legacy SCADA machines and pushing it to the Cloud for modern machine learning analytics without disrupting the factory floor. + +--- + +## 4. Security Focus: Why IoT is a Hacker's Dream + +IoT devices are famously insecure. Why? Because manufacturers rush to release $10 smart cameras and don't want to spend money on security software. + +### The Mirai Botnet (A Case Study) +In 2016, a massive portion of the global internet went down. The attack wasn't generated by supercomputers; it was generated by 600,000 hacked Smart Cameras and IoT routers. +Hackers scanned the internet for devices using default passwords (like `admin` / `admin`). Because the devices had weak security, the hackers installed malware on them, turning them into a "Botnet" (a robot network) that launched a coordinated DDoS attack. + +### The Three Pillars of IoT Security +1. **Never use Default Passwords**: Every device must generate a unique password or enforce a setup password change. +2. **Physical Security**: If a hacker can unscrew your smart lock and plug a USB cable into the microcontroller, it's game over. Disable UART/USB data ports in production firmware. +3. **Encryption (TLS/SSL)**: If your ESP32 sends a password to the Cloud over standard HTTP, the text is visible to anyone on the network. You must use HTTPS (TLS) to mathematically scramble the data so only the Cloud server can read it. + +### Visual Flow: TLS Encryption Architecture +![Placeholder: TLS Encryption Diagram](/iot/images/placeholder-m9-tls-flow.png) +*Visual Connection Flow: The raw data (e.g., {"temp": 800}) is fed into the ESP32's cryptography engine. It is encrypted into cipher-text before it ever hits the Wi-Fi antenna, ensuring complete privacy.* + +--- + +## 5. Practical Engineering Insights: OTA Updates + +Imagine you deploy 5,000 smart agriculture sensors across a country. Six months later, a major security flaw is discovered in the firmware. You cannot afford to send technicians to plug a USB cable into 5,000 devices in the middle of nowhere. + +The solution is **OTA (Over-The-Air) Updates**. +Professional IoT firmware is designed to periodically check the Cloud for a `.bin` update file. If an update exists, the microcontroller downloads the file over Wi-Fi, overwrites its own flash memory, and reboots with the new code. + +--- + +## 6. Interview-Style Conceptual Questions + +1. **Why does encrypting data (TLS) reduce the battery life of an IoT device?** + *Answer: Mathematical encryption (like AES-256) requires significant processing power. The microcontroller's CPU has to work much harder for longer periods to scramble the data before transmitting it, draining the battery faster.* +2. **If an industrial robot arm uses an IoT sensor to avoid hitting human workers, should that sensor connect via Wi-Fi?** + *Answer: No. Wi-Fi is susceptible to interference, jamming, and latency spikes. Safety-critical systems must use hardwired connections or highly specialized, guaranteed-delivery industrial wireless protocols.* + +--- + +## 7. Practical Project: Secure Industrial Machine Monitoring + +We will build an industrial-grade temperature monitor. Instead of a cheap DHT11, we will simulate a high-temperature K-Type Thermocouple reading using an Analog Pin. More importantly, we will secure the data transmission using **HTTPS (TLS/SSL)** instead of standard HTTP. + +### Hardware Spotlight: K-Type Thermocouple & MAX6675 +![Placeholder: K-Type Thermocouple](/iot/images/placeholder-m9-sensor.png) +*An industrial K-Type Thermocouple connected to a MAX6675 amplifier. Unlike cheap DHT sensors, the metal probe of a thermocouple can be physically submerged in boiling liquids or molten metals up to 1000°C without melting.* + +### Required Components +- 1x ESP32 Development Board +- 1x Potentiometer (To simulate the varying voltage of an industrial thermocouple) + +### Circuit Connection & Breadboard Visual +![Placeholder: Thermocouple Simulator Wiring](/iot/images/placeholder-m9-wiring.png) +*Visual Connection Flow: We are using a potentiometer to simulate the high temperatures. As you turn the knob, the analog voltage sent to Pin 34 changes. In a real factory, this would be the amplified analog output from the thermocouple.* + +- Connect the outer legs of the potentiometer to `3.3V` and `GND`. +- Connect the middle leg (wiper) to ESP32 Analog Pin `34`. + +### Step-by-Step Code Implementation + +> [!WARNING] +> Standard `HTTPClient` sends data in plain text. We will use `WiFiClientSecure` to encrypt our data payload before it leaves the ESP32. + +```cpp +#include +#include +#include + +const char* ssid = "YOUR_WIFI"; +const char* password = "YOUR_PASSWORD"; + +// We are simulating an industrial Cloud API endpoint +const char* serverUrl = "https://jsonplaceholder.typicode.com/posts"; + +// For true security, you must provide the Cloud Server's Root CA Certificate. +// This proves the server is who it claims to be (preventing Man-in-the-Middle attacks). +const char* rootCACertificate = \ +"-----BEGIN CERTIFICATE-----\n" \ +"MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\n" \ +"... (Your Server's CA Certificate Goes Here) ...\n" \ +"-----END CERTIFICATE-----\n"; + +const int thermocouplePin = 34; +const int CRITICAL_TEMP = 850; // Degrees Celsius + +void setup() { + Serial.begin(115200); + + WiFi.begin(ssid, password); + Serial.print("Connecting to secure Wi-Fi..."); + while(WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } + Serial.println("\nConnected!"); +} + +void loop() { + // 1. Read the "Thermocouple" + int rawADC = analogRead(thermocouplePin); + + // Convert ADC (0-4095) to Industrial Temp Range (0-1000C) + int machineTemp = map(rawADC, 0, 4095, 0, 1000); + + Serial.print("Machine Core Temperature: "); + Serial.print(machineTemp); + Serial.println(" C"); + + // 2. Security Protocol (TLS/SSL) + if(WiFi.status() == WL_CONNECTED) { + WiFiClientSecure *client = new WiFiClientSecure; + + // Set the certificate so the ESP32 verifies the server's identity + client->setCACert(rootCACertificate); + + HTTPClient https; + Serial.print("[HTTPS] begin...\n"); + + // Connect to the secure URL + if (https.begin(*client, serverUrl)) { + + // We are sending a JSON payload securely! + https.addHeader("Content-Type", "application/json"); + String jsonPayload = "{\"device\":\"Motor_A\", \"temp\":" + String(machineTemp) + "}"; + + Serial.print("[HTTPS] POST encrypted data...\n"); + int httpCode = https.POST(jsonPayload); + + if (httpCode > 0) { + Serial.printf("[HTTPS] POST... code: %d\n", httpCode); + } else { + Serial.printf("[HTTPS] POST... failed, error: %s\n", https.errorToString(httpCode).c_str()); + } + + https.end(); + } else { + Serial.printf("[HTTPS] Unable to connect\n"); + } + + delete client; // Free memory + } + + // 3. Failsafe Edge Logic (In case HTTPS fails) + if (machineTemp > CRITICAL_TEMP) { + Serial.println("EMERGENCY: LOCAL SHUTDOWN INITIATED."); + // Code to trigger a local physical relay to cut power to the machine + } + + delay(5000); // Wait 5 seconds +} +``` + +### Code Explanation & Data Flow +1. **`WiFiClientSecure`**: Unlike the basic HTTP client we used in Module 6, this library invokes a heavy cryptography engine inside the ESP32. +2. **The `rootCACertificate`**: If a hacker tries to intercept the Wi-Fi signal and pretend to be your Cloud Server, the ESP32 will check their digital certificate. If it doesn't match the hardcoded `rootCACertificate`, the ESP32 will abort the connection, preventing data theft. +3. **The Payload**: The JSON string `{"device":"Motor_A", "temp":800}` is completely scrambled into mathematical gibberish before it travels through the air to the Wi-Fi router. + +This is the standard required for production-grade Industrial IoT devices. + +--- +**[End of Module 9]** diff --git a/notes/iot/module-10-capstone.md b/notes/iot/module-10-capstone.md new file mode 100644 index 0000000..5ad4a68 --- /dev/null +++ b/notes/iot/module-10-capstone.md @@ -0,0 +1,199 @@ +# Module 10: Advanced Real-World Projects & Course Summary + +## 1. Introduction +Congratulations! You have journeyed from the absolute basics of turning on an LED, through Wi-Fi connectivity, MQTT messaging, Cloud Analytics, and Industrial Security. You are now equipped with the core skills required to build production-grade IoT systems. + +In this final capstone module, we will bring everything together. We will discuss System Architecture design for large-scale deployments and build a highly complex, multi-layered project: **An IoT Health Monitoring System**. + +--- + +## 2. Learning Objectives +By the end of this module, you will: +- Understand how to architect a large-scale, multi-layered IoT deployment. +- Master the integration of Edge Computing, M2M, and Cloud Analytics in a single system. +- Build a life-saving IoT Health Monitoring System. +- Review and summarize the core principles of the entire course. + +--- + +## 3. Core Concepts: The Enterprise Architecture + +When you build a system with 10,000 sensors across a smart city, you don't connect all 10,000 sensors directly to AWS via Wi-Fi. It is too expensive and inefficient. You build a **Multi-Layered Architecture**. + +### Visual Flow: Enterprise IoT Architecture +![Placeholder: Enterprise Architecture Diagram](/iot/images/placeholder-m10-architecture.png) +*Visual Connection Flow: Notice how Edge Devices (Sensors) talk to local Gateways via LoRa/BLE, and only the Gateways talk to the Cloud via 5G/Ethernet. This preserves battery life on the edge devices.* + +### Layer 1: The Edge (Sensors & Actuators) +- Thousands of ultra-low-power sensors (e.g., parking space detectors running on coin cell batteries). +- They communicate via **LoRaWAN** or **BLE** (Bluetooth Low Energy) to save power. +- They have *no direct internet access*. + +### Layer 2: The Gateway (The Local Hub) +- A powerful industrial computer (like a Raspberry Pi or an Industrial Edge Router) placed in the neighborhood. +- It receives the low-power LoRa/BLE signals from the sensors. +- It runs a **Local Rule Engine** (Edge Analytics) to filter out garbage data. +- It communicates with the Cloud via high-bandwidth **Ethernet or 5G**. + +### Layer 3: The Cloud (Storage & ML) +- Receives aggregated data from hundreds of Gateways. +- Stores the data in a Time-Series Database. +- Runs heavy Machine Learning algorithms (Predictive Maintenance). + +### Layer 4: The Application (The User) +- Mobile Apps and Web Dashboards pulling data from the Cloud APIs. + +--- + +## 4. Practical Project: IoT Health Monitoring System + +We will build a wearable patient monitor. It will read a patient's heart rate, blood oxygen (SpO2), and body temperature. +It will use Edge Analytics to detect if the patient's heart stops (anomaly detection) and trigger a local buzzer instantly (Failsafe). Concurrently, it will stream the data via Wi-Fi to a ThingSpeak Cloud Dashboard for doctors to monitor remotely. + +### Hardware Spotlight: MAX30102 Oximeter & I2C +![Placeholder: MAX30102 Sensor](/iot/images/placeholder-m10-sensor.png) +*The MAX30102 uses an LED to shine red and infrared light into your skin. It then measures the reflected light to calculate heart rate and blood oxygen. It uses the I2C protocol, meaning multiple sensors can share the same two data pins (SDA/SCL).* + +### Required Components +- 1x ESP32 Development Board +- 1x MAX30102 Pulse Oximeter & Heart Rate Sensor (I2C Protocol) +- 1x MLX90614 Contactless Temperature Sensor (I2C Protocol) +- 1x Active Buzzer +- ThingSpeak Cloud Account + +### Circuit Connection (I2C Protocol) & Breadboard Visual +![Placeholder: Health Monitor Wiring](/iot/images/placeholder-m10-wiring.png) +*Visual Connection Flow: Notice how BOTH the MAX30102 and the MLX90614 have their SDA pins connected to ESP32 Pin 21, and their SCL pins connected to ESP32 Pin 22. This is the magic of the I2C "bus" system!* + +I2C (Inter-Integrated Circuit) allows multiple sensors to be connected to the *exact same two pins*. The ESP32 talks to them using unique addresses (like house numbers). +- Connect **VCC** of both sensors to ESP32 `3.3V`. +- Connect **GND** of both sensors to ESP32 `GND`. +- Connect **SDA** of both sensors to ESP32 Pin `21`. +- Connect **SCL** of both sensors to ESP32 Pin `22`. +- Connect **Buzzer** positive to ESP32 Pin `19`, negative to `GND`. + +### Step-by-Step Code Implementation + +> [!TIP] +> You need the `SparkFun MAX3010x` library and `Adafruit MLX90614` library installed. + +```cpp +#include +#include +#include +#include "MAX30105.h" +#include "heartRate.h" +#include + +const char* ssid = "YOUR_WIFI"; +const char* password = "YOUR_WIFI_PASSWORD"; +String apiKey = "YOUR_THINGSPEAK_API_KEY"; + +MAX30105 particleSensor; +Adafruit_MLX90614 mlx = Adafruit_MLX90614(); + +const int buzzerPin = 19; +const byte RATE_SIZE = 4; // Array to calculate average heart rate +byte rates[RATE_SIZE]; +byte rateSpot = 0; +long lastBeat = 0; +float beatsPerMinute; +int beatAvg; + +unsigned long lastCloudUpdate = 0; +const int CLOUD_DELAY = 15000; // Update cloud every 15 seconds + +void setup() { + Serial.begin(115200); + pinMode(buzzerPin, OUTPUT); + Wire.begin(21, 22); // Start I2C on pins 21 and 22 + + // Initialize Sensors + if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) { + Serial.println("MAX30102 was not found. Please check wiring/power."); + while (1); + } + particleSensor.setup(); + particleSensor.setPulseAmplitudeRed(0x0A); // Turn Red LED to low to indicate sensor is running + + mlx.begin(); + + // Connect WiFi + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } + Serial.println("\nWiFi Connected."); +} + +void loop() { + // 1. Data Collection (Read Sensors continuously) + long irValue = particleSensor.getIR(); + float bodyTemp = mlx.readObjectTempC(); + + // 2. Edge Analytics: Heart Rate Calculation + if (checkForBeat(irValue) == true) { + long delta = millis() - lastBeat; + lastBeat = millis(); + + beatsPerMinute = 60 / (delta / 1000.0); + + if (beatsPerMinute < 255 && beatsPerMinute > 20) { + rates[rateSpot++] = (byte)beatsPerMinute; + rateSpot %= RATE_SIZE; + + // Calculate average + beatAvg = 0; + for (byte x = 0 ; x < RATE_SIZE ; x++) beatAvg += rates[x]; + beatAvg /= RATE_SIZE; + } + } + + // 3. Failsafe Rule Engine: Local Anomaly Detection + // If no finger is detected (IR < 50000) or Temp is fatally high/low + if (irValue > 50000 && (beatAvg < 40 || beatAvg > 160 || bodyTemp > 39.5)) { + Serial.println("MEDICAL EMERGENCY DETECTED!"); + digitalWrite(buzzerPin, HIGH); // Sound local alarm immediately + } else { + digitalWrite(buzzerPin, LOW); + } + + // 4. Cloud Integration (Send data every 15 seconds) + if (millis() - lastCloudUpdate > CLOUD_DELAY) { + if(WiFi.status() == WL_CONNECTED && irValue > 50000) { + HTTPClient http; + String url = "http://api.thingspeak.com/update?api_key=" + apiKey + "&field1=" + String(beatAvg) + "&field2=" + String(bodyTemp); + + http.begin(url); + int httpCode = http.GET(); + if (httpCode > 0) { + Serial.println("Cloud Updated. BPM: " + String(beatAvg) + " Temp: " + String(bodyTemp)); + } + http.end(); + } + lastCloudUpdate = millis(); + } +} +``` + +### System Flow Breakdown +1. **The Physical Layer (I2C)**: The ESP32 rapidly polls the MAX30102 via I2C, reading the raw Infrared light reflecting off the blood vessels in the patient's finger. +2. **The Controller (Edge Analytics)**: The ESP32 runs the `checkForBeat()` algorithm locally, converting raw IR spikes into a usable BPM (Beats Per Minute) number. +3. **The Action (Failsafe)**: If the BPM is outside safe bounds, the ESP32 instantly turns on the buzzer. It does not ask the Cloud for permission. This is critical for life-safety. +4. **The Cloud (Logging)**: Every 15 seconds, the ESP32 pushes the averaged BPM and body temperature to ThingSpeak. A doctor in another country can look at the historical graph and notice a gradual increase in temperature over 3 hours, indicating an infection. + +--- + +## 5. Course Revision Summary + +As you embark on your own IoT projects, remember the Golden Rules you learned in this course: + +1. **The Flow**: Every system is `Sensor → Controller → Network → Cloud → Actuator`. +2. **Voltage**: Microcontrollers read voltage, not physical phenomena. Understand the difference between Analog (Continuous) and Digital (Binary). +3. **Power is Everything**: Use Deep Sleep. If you are running on batteries, Wi-Fi is your enemy; BLE and LoRa are your friends. +4. **Don't Block the Loop**: Never use `delay(10000)`. Use `millis()` timing so your microcontroller can multitask. +5. **Edge over Cloud**: Never rely on Wi-Fi for safety-critical logic. Build local failsafes into your edge devices. +6. **Security by Design**: Never use default passwords. Always encrypt sensitive data using TLS/SSL over HTTPS or secure MQTT (Port 8883). + +You are no longer a beginner. You are an IoT Engineer. Now, go build the future. + +--- +**[End of Course]** diff --git a/package-lock.json b/package-lock.json index 4b72912..b4d9764 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,9 @@ "lucide-react": "^0.544.0", "next": "15.5.9", "react": "19.1.0", - "react-dom": "19.1.0" + "react-dom": "19.1.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -1260,13 +1262,39 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1281,6 +1309,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.13.tgz", @@ -1295,7 +1338,6 @@ "version": "19.1.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1311,6 +1353,12 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.42.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.42.0.tgz", @@ -1599,6 +1647,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "license": "ISC" + }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", @@ -2154,6 +2208,16 @@ "node": ">= 0.4" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2265,6 +2329,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2282,6 +2356,46 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -2343,6 +2457,16 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2369,7 +2493,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -2437,7 +2560,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2451,6 +2573,19 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2494,6 +2629,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -2504,6 +2648,19 @@ "node": ">=8" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -3159,6 +3316,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3169,6 +3336,12 @@ "node": ">=0.10.0" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3585,6 +3758,56 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3622,6 +3845,12 @@ "node": ">=0.8.19" } }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -3637,6 +3866,30 @@ "node": ">= 0.4" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -3789,6 +4042,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3847,6 +4110,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -3900,6 +4173,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -4463,6 +4748,16 @@ "dev": true, "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4495,6 +4790,16 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4505,97 +4810,941 @@ "node": ">= 0.4" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "license": "MIT", - "engines": { - "node": ">= 8" + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, "engines": { - "node": ">=8.6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" }, - "engines": { - "node": "*" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "dev": true, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "license": "MIT", "dependencies": { - "minipass": "^7.1.2" + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" }, - "engines": { - "node": ">= 18" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/ms": { + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -4923,6 +6072,31 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5030,6 +6204,16 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5089,6 +6273,33 @@ "dev": true, "license": "MIT" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5133,6 +6344,72 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -5493,6 +6770,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -5627,6 +6914,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -5650,6 +6951,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -5799,6 +7118,26 @@ "node": ">=8.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -5962,6 +7301,93 @@ "dev": true, "license": "MIT" }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unrs-resolver": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", @@ -6007,6 +7433,34 @@ "punycode": "^2.1.0" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6144,6 +7598,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 0fb9526..02c5bca 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "lucide-react": "^0.544.0", "next": "15.5.9", "react": "19.1.0", - "react-dom": "19.1.0" + "react-dom": "19.1.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/public/iot/images/.gitkeep b/public/iot/images/.gitkeep new file mode 100644 index 0000000..e69de29