diff --git a/GROUP.md b/GROUP.md index 8efe854..2357bcd 100644 --- a/GROUP.md +++ b/GROUP.md @@ -1,4 +1,5 @@ # Group Members -1. Name (https://github.com/name/iotclass67) -1. Name (https://github.com/name/iotclass67) -1. Name (https://github.com/name/iotclass67) +1. 6410301026 (https://github.com/Thanabodin19/iotclass67.git) +2. 6410301029 (https://github.com/gunnvrs/iotclass67.git) +3. 6410301031 (https://github.com/sariszoom/iotclass67.git) + diff --git a/README.md b/README.md index 9bbc2ed..864c7c1 100644 --- a/README.md +++ b/README.md @@ -17,4 +17,4 @@ Iot Event Streaming Architecture.[^1] GitHub format syntax[^2]. [^1]: https://sanchezsanchezsergio418.medium.com/iot-event-streaming-architecture-fb790c634c2f -[^2]: Visit https://docs.github.com/en/get-started/writing-on-github +[^2]: Visit https://docs.github.com/en/get-started/writing-on-github \ No newline at end of file diff --git a/assignment00/architecture.md b/assignment00/architecture.md index 28dce73..71015e6 100644 --- a/assignment00/architecture.md +++ b/assignment00/architecture.md @@ -6,32 +6,77 @@ ## Eclipse Mosquitto - +**Eclipse Mosquitto** เป็นซอฟต์แวร์โอเพ่นซอร์สที่ทำหน้าที่เป็น MQTT broker ซึ่งเป็นโปรโตคอลที่ใช้ในการสื่อสารระหว่างอุปกรณ์ IoT ด้วยการออกแบบที่มีประสิทธิภาพและใช้ทรัพยากรน้อย ทำให้ Eclipse Mosquitto เหมาะสำหรับงานที่เกี่ยวกับ IoT โดยมีคุณสมบัติหลักดังนี้ +- **โอเพ่นซอร์ส** ดาวน์โหลดและใช้งานได้ฟรี รวมถึงแก้ไขซอฟต์แวร์ได้ตามต้องการ +- **รองรับ MQTT** ทำหน้าที่เป็นตัวกลางในการส่งและรับข้อความระหว่างอุปกรณ์ +- **ติดตั้งง่าย** รองรับหลายระบบปฏิบัติการ เช่น Windows, macOS, และ Linux +- **ความปลอดภัย** รองรับการเข้ารหัส SSL/TLS และการตรวจสอบสิทธิ์ของผู้ใช้ ## Apache ZooKeeper +**Apache Zookeeper** เป็นระบบจัดการและประสานงานแบบกระจาย (distributed coordination service) โดยใช้ในระบบที่มีความซับซ้อนและต้องการความเสถียรสูง เช่น Apache Kafka, Hadoop, และ Hbase +หน้าที่และคุณสมบัติสำคัญของ ZooKeeper ได้แก่ +- จัดการ brokers และบันทึกสถานะของ brokers แต่ละตัว +- บันทึกข้อมูลเกี่ยวกับ topics และ partitions +- เลือก leader/replica ของ partitions +- แจ้งเตือน Kafka เมื่อมีการเปลี่ยนแปลง เช่น broker ใหม่หรือตาย +- บันทึกการเขียน/อ่านข้อมูลของ producers และ consumers +- เก็บข้อมูล Authorization สำหรับการสร้าง topics +- ติดตาม offset ของ consumer groups ## Apache Kafka - +**Apache Kafka** คือระบบจัดคิวข้อความแบบกระจาย (distributed message queue) ที่สามารถรับส่งข้อมูลในปริมาณมากได้อย่างมีประสิทธิภาพ Kafka เหมาะสำหรับระบบที่ต้องการความสามารถในการส่งข้อมูลระหว่างแอปพลิเคชันหลายตัวในรูปแบบที่เสถียรและปรับขนาดได้ ## Apache Kafka Connect - +**Kafka Connect** เป็นเครื่องมือที่ทำหน้าที่เชื่อมต่อและแปลงข้อมูลระหว่างระบบต้นทาง (source systems) และระบบปลายทาง (target systems) โดยไม่ต้องสร้าง producer และ consumer ขึ้นมาเอง Kafka Connect ช่วยลดความซับซ้อนในการทำงานกับข้อมูลจำนวนมากที่อยู่ใน Kafka ## Apache Kafka Streams +**Kafka Streams** เป็นไลบรารีที่ใช้สำหรับการประมวลผลข้อมูลใน Kafka โดยสามารถทำการคำนวณและแปลงข้อมูลแบบสตรีมได้อย่างมีประสิทธิภาพ +## Apache Kafka Rest Proxy +**Kafka REST Proxy** เป็นเครื่องมือหรืออินเทอร์เฟซที่ช่วยให้ผู้ใช้สามารถสื่อสารกับ Kafka brokers ผ่าน HTTP API แทนที่จะใช้ Kafka client โดยตรง ซึ่งทำให้การเชื่อมต่อกับ Kafka ง่ายขึ้นสำหรับแอปพลิเคชันหรือบริการที่ไม่รองรับ Kafka client libraries หรือไม่ได้เขียนด้วยภาษาที่ Kafka รองรับโดยตรง ## Prometheus - +**Prometheus** เป็นระบบมอนิเตอร์ริ่งและเตือนภัยแบบโอเพ่นซอร์สที่ออกแบบมาเพื่อรวบรวมและจัดเก็บข้อมูลเมตริกในรูปแบบ time series data เหมาะสำหรับการมอนิเตอร์ระบบและแอปพลิเคชันต่าง ๆ โดยมีการทำงานแบบกระจายและมีความยืดหยุ่นสูง Prometheus สามารถทำงานร่วมกับ Grafana เพื่อสร้างกราฟแสดงข้อมูลแบบเรียลไทม์ ## MongoDB - +**MongoDB** เป็นฐานข้อมูล NoSQL ที่ได้รับความนิยมซึ่งออกแบบมาให้จัดเก็บข้อมูลในรูปแบบที่ยืดหยุ่น MongoDB เหมาะสำหรับการจัดการข้อมูลปริมาณมาก โดยนักพัฒนาสามารถปรับแต่งการทำงานของระบบเพื่อสร้างแอปพลิเคชันที่ทันสมัยและขับเคลื่อนด้วยข้อมูลได้อย่างง่ายดาย ## Grafana +**Grafana** เป็นเครื่องมือสร้างแดชบอร์ดที่สามารถแสดงข้อมูลจากเมตริกต่าง ๆ ในรูปแบบกราฟแบบเรียลไทม์ Grafana รองรับการดึงข้อมูลจากแหล่งข้อมูลยอดนิยม เช่น Prometheus, InfluxDB, Elasticsearch, AWS CloudWatch เป็นต้น + +จุดเด่นของ Grafana คือ +- **การมอนิเตอร์เมตริกแบบเรียลไทม์** สามารถดู CPU, Memory และเมตริกอื่น ๆ ได้ทันที +- **การแจ้งเตือน** รองรับการแจ้งเตือนผ่าน Email, Slack, Line และช่องทางอื่น ๆ +- **ความยืดหยุ่นสูง** มีตัวเลือกในการปรับแต่งกราฟและแผงควบคุม (panels) อย่างหลากหลาย +- **รองรับหลาย Data Sources** สามารถใช้งานกับแหล่งข้อมูลหลายประเภทพร้อมกันได้ + +## IoT Processor + +**IoT Processor** เป็นการประมวลผลข้อมูลที่ได้จากเซ็นเซอร์ ข้อมูลที่เก็บมาอาจจะถูกส่งไปยังระบบประมวลผลหรือเซิร์ฟเวอร์กลาง การประมวลผลอาจรวมถึง + +- การคำนวณ: เช่นการคำนวณค่าเฉลี่ย หรือค่าความผันแปรของข้อมูล +- การแปลงข้อมูล: เช่น การแปลงหน่วยข้อมูลที่ได้จากเซ็นเซอร์ +- การจัดส่งข้อมูล: ส่งข้อมูลไปยัง MQTT broker เพื่อนำไปแสดงผลบน Grafana หรือเก็บในฐานข้อมูล +## IoT Sensor +ในโปรเจคนี้เรามี IoT Sensor 2 รูปแบบคือ +* ข้อมูล IoT Sensor จาก hardware (Cucumber) +* ข้อมูล IoT Sensor จาก ไมโครเซอร์วิส ที่จำลองจาก Container +### Cucumber +IoT Sensor เป็นโมดูลเซ็นเซอร์หรือฟังก์ชันที่เกี่ยวกับการรวบรวมข้อมูลจากเซ็นเซอร์ต่างๆบนอุปกรณ์ hardware รวมถึงการอ่านค่าในระบบ IoT ตัวอย่างเช่น + +- อุณหภูมิ: การรวบรวมอุณหภูมิจากเซ็นเซอร์อุณหภูมิ เช่น DHT11, BMP280 +- ความชื้น: การเก็บค่าความชื้นในอากาศ +- แสง: การเก็บข้อมูลความเข้มของแสงจาก LDR ที่ทำการต่อเพิ่ม(cucumberไม่มีbuilt-in) +- ความกดอากาศ: atm + +### IoT Sensor container +IoT Sensor เป็น sensor ที่ถูกจําลองด้วยไมโครเซอร์วิสที่ใช้ใน Spring Boot (ผ่านไลบรารี Eclipse Paho MQTT) ที่ถูกติดตั้งอยู่บนเซิฟเวอร์ โดยจะส่งข้อมูล telemetry ไปยังโบรกเกอร์ Eclipse Mosquitto ข้อมูลที่ถูกจำลองนี้ generate ค่า ทุกอย่างภายใน payload มาจาก Callable โดยจะถูกสร้างขึ้นทุกวินาทีและ มี payload ในรูปแบบที่สร้างขึ้นให้ตรงกัน diff --git a/assignment01/01-install-server.md b/assignment01/01-install-server.md index a57bdc9..0662578 100644 --- a/assignment01/01-install-server.md +++ b/assignment01/01-install-server.md @@ -1,14 +1,177 @@ # Install Server and Docker +## How to install Server 🖥️ +### Install Ubuntu 24.04 (LTS) on server -## How to install Server +#### 1. Select Your Language +Choose your preferred language. + - Example: `[ English ]` +#### 2. Installer Update Available +Skip the update for now. + - Select: `[ Continue without updating ]` +#### 3. Keyboard Configuration +If the default keyboard layout is correct, proceed. + - Select: `[ Done ]` +#### 4. Choose the Type of Installation +Choose the minimized Ubuntu Server installation. + - Option: `(X) Ubuntu Server (minimized)` + - Select: `[ Done ]` +#### 5. Network Configuration +Accept the default network configuration. + - Select: `[ Done ]` -## How to install Docker +#### 6. Proxy Configuration +If you don't use a proxy, leave this blank. + - Select: `[ Done ]` +#### 7. Ubuntu Archive Mirror Configuration +Accept the default mirror settings. + - Select: `[ Done ]` +#### 8. Guided Storage Configuration +Choose to create a custom storage layout. + - Option: `(X) Custom storage layout` + - Select: `[ Done ]` +#### 9. Storage Configuration +Configure your partitions as follows: +1. **Mount Boot Partition:** + - Use free space to create a new GPT partition. + - Size: `2G` + - Format: `[ ext4 ]` + - Mount: `[ /boot ]` + - Select: `[ Create ]` + +2. **Mount Swap Partition:** + - Use free space to create a new GPT partition. + - Size: `2G` + - Format: `[ swap ]` + - Select: `[ Create ]` + +3. **Mount Root Partition:** + - Use the remaining free space to create a new GPT partition. + - Size: `MAX` + - Format: `[ ext4 ]` + - Mount: `[ / ]` + - Select: `[ Create ]` + +#### **File System Summary:** + +| MOUNT POINT | SIZE | TYPE | DEVICE TYPE | +|-------------|-------|------|------------------------| +| / | MAX | ext4 | partition of localdis | +| /boot | 2.0 G | ext4 | partition of localdis | +| /SWAP | 2.0 G | swap | partition of localdis | + +- Select: `[ Done ]` +- Confirm the destructive action. + - Select: `[ Continue ]` + +#### 10. Profile Configuration +Fill in the following fields to set up your server profile: + +- **Your name:** Enter your full name. +- **Your server name:** Choose a name for your server. +- **Pick a username:** Choose a username for your account. +- **Choose a password:** Create a secure password. +- **Confirm your password:** Re-enter your password to confirm. +- Select: `[ Done ]` + +#### 11. Upgrad to Ubunto Pro +If prompted, select the option for upgrading to Ubuntu Pro. +- Option: `(X) Custom storage layout` +- Select: `[ Continue ]` + +#### 12. SSH Configuration +Install the OpenSSH server to enable remote access. +- Option: `(X) Install OpenSSH server` +- Select: `[ Done ]` + +#### 13. Featured server snaps +Skip the installation of featured snaps. +- Select: `[ Continue ]` + +#### 14. Installation Complete! +Once the installation is finished, your server is ready to use. +- Select: `[ Reboot Now ]` + +--- + + + +### Setup Basic tools and Clone Project +``` bash + # install wireless-tools, net-tools, git, vim + sudo apt install wireless-tools net-tools git vim + + # git clone project + git clone https://github.com/sergio11/iot_event_streaming_architecture.git + + # into project + cd iot_event_streaming_architecture + +``` + +## How to install Docker 🐳 + +``` bash + # Add Docker's official GPG key: + sudo apt-get update + sudo apt-get install ca-certificates curl + sudo install -m 0755 -d /etc/apt/keyrings + sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc + sudo chmod a+r /etc/apt/keyrings/docker.asc + + # Add the repository to Apt sources: + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + + # Install the Docker packages: + sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin +``` +### Manage Docker as a non-root user +To create the `docker` group and add your user: +1. Create the `docker` group. +``` bash +sudo groupadd docker +``` +2. Add your user to the `docker` group. +```bash +sudo usermod -aG docker $USER +``` +3. Log out and log back in so that your group membership is re-evaluated. +```bash +newgrp docker +``` + +### Set up DNS Docker and Docker login +``` bash + # use vim edit file + sudo vim /etc/docker/daemon.json + + # Put this text in the file. + { + "dns": ["8.8.8.8", "8.8.4.4"] + } + + # restart service docker + sudo systemctl restart docker + + # docker login + sudo docker login +``` + +## How to Uninstall Docker Engine +``` bash + # Uninstall the Docker Engine, CLI, containerd, and Docker Compose packages: + sudo apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras + + # Images, containers, volumes, or custom configuration files on your host aren't automatically removed. To delete all images, containers, and volumes: + sudo rm -rf /var/lib/docker + sudo rm -rf /var/lib/containerd +``` diff --git a/assignment01/02-docker-compose-iot.md b/assignment01/02-docker-compose-iot.md index 9075302..f266609 100644 --- a/assignment01/02-docker-compose-iot.md +++ b/assignment01/02-docker-compose-iot.md @@ -1,39 +1,311 @@ # IoT Docker compose + +### docker-compose.yaml +```yaml +services: + # zookeeper เป็นระบบจัดการและประสานงานแบบกระจาย (distributed coordination service) โดยใช้ในระบบที่มีความซับซ้อนและต้องการความเสถียรสูง เช่น Apache Kafka, Hadoop, และ Hbase + zookeeper: + image: confluentinc/cp-zookeeper + container_name: zookeeper + restart: unless-stopped + volumes: + - zookeeper-data:/var/lib/zookeeper/data + - zookeeper-log:/var/lib/zookeeper/log + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_LOG4J_ROOT_LOGLEVEL: INFO + ZOOKEEPER_LOG4J_PROP: INFO,ROLLINGFILE + ZOOKEEPER_LOG_MAXFILESIZE: 10MB + ZOOKEEPER_LOG_MAXBACKUPINDEX: 10 + ZOOKEEPER_SNAP_COUNT: 10 + ZOOKEEPER_AUTOPURGE_SNAP_RETAIN_COUNT: 10 + ZOOKEEPER_AUTOPURGE_PURGE_INTERVAL: 3 + + # Kafka คือระบบจัดคิวข้อความแบบกระจาย (distributed message queue) ที่สามารถรับส่งข้อมูลในปริมาณมากได้อย่างมีประสิทธิภาพ Kafka เหมาะสำหรับระบบที่ต้องการความสามารถในการส่งข้อมูลระหว่างแอปพลิเคชันหลายตัวในรูปแบบที่เสถียรและปรับขนาดได้ + kafka: + image: confluentinc/cp-kafka + container_name: kafka + volumes: + - kafka-data:/var/lib/kafka + restart: unless-stopped + environment: + # Required. Instructs Kafka how to get in touch with ZooKeeper. + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_NUM_PARTITIONS: 1 + KAFKA_COMPRESSION_TYPE: gzip + # Required when running in a single-node cluster, as we are. We would be able to take the default if we had + # three or more nodes in the cluster. + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + # Required. Kafka will publish this address to ZooKeeper so clients know + # how to get in touch with Kafka. "PLAINTEXT" indicates that no authentication + # mechanism will be used. + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 + KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true' + links: + - zookeeper + # kafka-rest-proxy เป็นเครื่องมือหรืออินเทอร์เฟซที่ช่วยให้ผู้ใช้สามารถสื่อสารกับ Kafka brokers ผ่าน HTTP API แทนที่จะใช้ Kafka client โดยตรง ซึ่งทำให้การเชื่อมต่อกับ Kafka ง่ายขึ้นสำหรับแอปพลิเคชันหรือบริการที่ไม่รองรับ Kafka client libraries หรือไม่ได้เขียนด้วยภาษาที่ Kafka รองรับโดยตรง + kafka-rest-proxy: + image: confluentinc/cp-kafka-rest:latest + container_name: kafka-rest-proxy + environment: + # Specifies the ZooKeeper connection string. This service connects + # to ZooKeeper so that it can broadcast its endpoints as well as + # react to the dynamic topology of the Kafka cluster. + KAFKA_REST_ZOOKEEPER_CONNECT: zookeeper:2181 + # The address on which Kafka REST will listen for API requests. + KAFKA_REST_LISTENERS: http://0.0.0.0:8082/ + # Required. This is the hostname used to generate absolute URLs in responses. + # It defaults to the Java canonical hostname for the container, which might + # not be resolvable in a Docker environment. + KAFKA_REST_HOST_NAME: kafka-rest-proxy + # The list of Kafka brokers to connect to. This is only used for bootstrapping, + # the addresses provided here are used to initially connect to the cluster, + # after which the cluster will dynamically change. Thanks, ZooKeeper! + KAFKA_REST_BOOTSTRAP_SERVERS: kafka:9092 + # Kafka REST relies upon Kafka, ZooKeeper + # This will instruct docker to wait until those services are up + # before attempting to start Kafka REST. + restart: unless-stopped + ports: + - "9999:8082" + depends_on: + - zookeeper + - kafka -## How to start docker compose + # kafka-connect เป็นเครื่องมือที่ทำหน้าที่เชื่อมต่อและแปลงข้อมูลระหว่างระบบต้นทาง (source systems) และระบบปลายทาง (target systems) โดยไม่ต้องสร้าง producer และ consumer ขึ้นมาเอง Kafka Connect ช่วยลดความซับซ้อนในการทำงานกับข้อมูลจำนวนมากที่อยู่ใน Kafka + kafka-connect: + image: confluentinc/cp-kafka-connect:latest + hostname: kafka-connect + container_name: kafka-connect + environment: + # Required. + # The list of Kafka brokers to connect to. This is only used for bootstrapping, + # the addresses provided here are used to initially connect to the cluster, + # after which the cluster can dynamically change. Thanks, ZooKeeper! + CONNECT_BOOTSTRAP_SERVERS: "kafka:9092" + # Required. A unique string that identifies the Connect cluster group this worker belongs to. + CONNECT_GROUP_ID: kafka-connect-group + # Connect will actually use Kafka topics as a datastore for configuration and other data. #meta + # Required. The name of the topic where connector and task configuration data are stored. + CONNECT_CONFIG_STORAGE_TOPIC: kafka-connect-meta-configs + # Required. The name of the topic where connector and task configuration offsets are stored. + CONNECT_OFFSET_STORAGE_TOPIC: kafka-connect-meta-offsets + # Required. The name of the topic where connector and task configuration status updates are stored. + CONNECT_STATUS_STORAGE_TOPIC: kafka-connect-meta-status + # Required. Converter class for key Connect data. This controls the format of the + # data that will be written to Kafka for source connectors or read from Kafka for sink connectors. + CONNECT_KEY_CONVERTER: org.apache.kafka.connect.json.JsonConverter + # Required. Converter class for value Connect data. This controls the format of the + # data that will be written to Kafka for source connectors or read from Kafka for sink connectors. + CONNECT_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter + # Required. The hostname that will be given out to other workers to connect to. + CONNECT_REST_ADVERTISED_HOST_NAME: "kafka-connect" + CONNECT_REST_PORT: 8083 + # The next three are required when running in a single-node cluster, as we are. + # We would be able to take the default (of 3) if we had three or more nodes in the cluster. + CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: "1" + CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: "1" + CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: "1" + #Connectos path + CONNECT_PLUGIN_PATH: "/usr/share/java,/data/connectors/" + CONNECT_LOG4J_ROOT_LOGLEVEL: "INFO" + restart: unless-stopped + volumes: + - ./kafka_connect/data:/data + command: + - bash + - -c + - | + echo "Launching Kafka Connect worker" + /etc/confluent/docker/run & + # + echo "Waiting for Kafka Connect to start listening on http://$$CONNECT_REST_ADVERTISED_HOST_NAME:$$CONNECT_REST_PORT/connectors ⏳" + while [ $$(curl -s -o /dev/null -w %{http_code} http://$$CONNECT_REST_ADVERTISED_HOST_NAME:$$CONNECT_REST_PORT/connectors) -ne 200 ] ; do + echo -e $$(date) " Kafka Connect listener HTTP state: " $$(curl -s -o /dev/null -w %{http_code} http://$$CONNECT_REST_ADVERTISED_HOST_NAME:$$CONNECT_REST_PORT/connectors) " (waiting for 200)" + sleep 5 + done + nc -vz $$CONNECT_REST_ADVERTISED_HOST_NAME $$CONNECT_REST_PORT + echo -e "\n--\n+> Creating Kafka Connect MongoDB sink Current PATH ($$PWD)" + /data/scripts/create_mongo_sink.sh + echo -e "\n--\n+> Creating MQTT Source Connect Current PATH ($$PWD)" + /data/scripts/create_mqtt_source.sh + echo -e "\n--\n+> Creating Kafka Connect Prometheus sink Current PATH ($$PWD)" + /data/scripts/create_prometheus_sink.sh + sleep infinity + # kafka-connect relies upon Kafka and ZooKeeper. + # This will instruct docker to wait until those services are up + # before attempting to start kafka-connect. + depends_on: + - zookeeper + - kafka + + # mosquitto เป็นซอฟต์แวร์โอเพ่นซอร์สที่ทำหน้าที่เป็น MQTT broker ซึ่งเป็นโปรโตคอลที่ใช้ในการสื่อสารระหว่างอุปกรณ์ IoT ด้วยการออกแบบที่มีประสิทธิภาพและใช้ทรัพยากรน้อย ทำให้ Eclipse Mosquitto เหมาะสำหรับงานที่เกี่ยวกับ IoT + mosquitto: + image: eclipse-mosquitto:latest + hostname: mosquitto + container_name: mosquitto + restart: unless-stopped + ports: + - "1883:1883" + - "9001:9001" + volumes: + - ./mosquitto/config:/mosquitto/config + - ./mosquitto/data:/mosquitto/data + - ./mosquitto/log:/mosquitto/log + + # mongo เป็นฐานข้อมูล NoSQL ที่ได้รับความนิยมซึ่งออกแบบมาให้จัดเก็บข้อมูลในรูปแบบที่ยืดหยุ่น MongoDB เหมาะสำหรับการจัดการข้อมูลปริมาณมาก โดยนักพัฒนาสามารถปรับแต่งการทำงานของระบบเพื่อสร้างแอปพลิเคชันที่ทันสมัยและขับเคลื่อนด้วยข้อมูลได้อย่างง่ายดาย + mongo: + image: mongo:4.4.20 + container_name: mongo + env_file: + - .env + restart: unless-stopped + environment: + - MONGO_INITDB_ROOT_USERNAME=${MONGO_ROOT_USER} + - MONGO_INITDB_ROOT_PASSWORD=${MONGO_ROOT_PASSWORD} + - MONGO_INITDB_DATABASE=${MONGO_DB} -```bash + # grafana เป็นเครื่องมือสร้างแดชบอร์ดที่สามารถแสดงข้อมูลจากเมตริกต่าง ๆ ในรูปแบบกราฟแบบเรียลไทม์ Grafana รองรับการดึงข้อมูลจากแหล่งข้อมูลยอดนิยม เช่น Prometheus, InfluxDB, Elasticsearch, AWS CloudWatch + grafana: + image: grafana/grafana:latest-ubuntu + container_name: grafana + user: '0' + volumes: + - ./grafana/data:/var/lib/grafana + - ./grafana/dashboards:/etc/grafana/provisioning/dashboards + - ./grafana/datasources:/etc/grafana/provisioning/datasources + - ./grafana/data/plugins:/var/lib/grafana/plugins + + environment: + - GF_SECURITY_ADMIN_USER=${ADMIN_USER:-admin} + - GF_SECURITY_ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin} + - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-worldmap-panel,grafana-piechart-panel + - GF_USERS_ALLOW_SIGN_UP=false + - GF_SECURITY_ANGULAR_SUPPORT_ENABLED=True + - GF_FEATURE_TOGGLES_ANGULARDEPRECATIONUI=False + restart: unless-stopped + links: + - prometheus + ports: + - '8085:3000' + + # prometheus เป็นระบบมอนิเตอร์ริ่งและเตือนภัยแบบโอเพ่นซอร์สที่ออกแบบมาเพื่อรวบรวมและจัดเก็บข้อมูลเมตริกในรูปแบบ time series data เหมาะสำหรับการมอนิเตอร์ระบบและแอปพลิเคชันต่าง ๆ โดยมีการทำงานแบบกระจายและมีความยืดหยุ่นสูง Prometheus สามารถทำงานร่วมกับ Grafana เพื่อสร้างกราฟแสดงข้อมูลแบบเรียลไทม์ + prometheus: + image: prom/prometheus:latest + container_name: prometheus + volumes: + - ./prometheus/:/etc/prometheus/ + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/etc/prometheus/console_libraries' + - '--web.console.templates=/etc/prometheus/consoles' + - '--storage.tsdb.retention.time=200h' + - '--web.enable-lifecycle' + restart: unless-stopped + ports: + - '8086:9090' + + # iot-processor เป็นการประมวลผลข้อมูลที่ได้จากเซ็นเซอร์ ข้อมูลที่เก็บมาอาจจะถูกส่งไปยังระบบประมวลผลหรือเซิร์ฟเวอร์กลาง การประมวลผลอาจรวมถึง + iot-processor: + image: ssanchez11/iot_processor:0.0.1-SNAPSHOT + container_name: iot-processor + restart: unless-stopped + ports: + - '8080:8080' + depends_on: + kafka-connect: + condition: service_started + restart: true + # IoT sensor 1 เป็น sensor ที่ถูกจําลองด้วยไมโครเซอร์วิสที่ใช้ใน Spring Boot (ผ่านไลบรารี Eclipse Paho MQTT) ที่ถูกติดตั้งอยู่บนเซิฟเวอร์ โดยจะส่งข้อมูล telemetry ไปยังโบรกเกอร์ Eclipse Mosquitto ข้อมูลที่ถูกจำลองนี้ generate ค่า ทุกอย่างภายใน payload มาจาก Callable โดยจะถูกสร้างขึ้นทุกวินาทีและ มี payload ในรูปแบบที่สร้างขึ้นให้ตรงกัน + iot_sensor_1: + image: ssanchez11/iot_sensor:0.0.1-SNAPSHOT + build: + context: ./microservices/iot_sensor + args: + - MQTT_SERVER=${IOT_SENSOR_1_ID} + container_name: iot_sensor_1 + restart: unless-stopped + environment: + - sensor.id=${IOT_SENSOR_1_ID} + - sensor.name=${IOT_SENSOR_1_NAME} + - sensor.place.id=${IOT_SENSOR_1_PLACE_ID} + - sensor.mqtt.username=${IOT_SENSOR_1_USERNAME} + - sensor.mqtt.password=${IOT_SENSOR_1_PASSWORD} + - MQTT_SERVER=${MQTT_SERVER} + depends_on: + iot-processor: + condition: service_started + restart: true ``` + + +## start-service #0 +```bash +sh start_0zookeeper_kafka.sh +``` +#### service +* [zookeeper](https://github.com/Thanabodin19/iotclass67/blob/main/assignment00/architecture.md#apache-zookeeper) +* [kafka](https://github.com/Thanabodin19/iotclass67/blob/main/assignment00/architecture.md#apache-kafka) -## Error we found +> [!NOTE] +> เมื่อรันคำสั่ง รอจนกว่า service kafka zookeeper จะนิ่งถึงจะรัน `start-service #1` ต่อไป -## How to solve the problems. +## start-service #1 +```bash +sh start_1kafka_service.sh +``` +#### service +* [kafka-rest-proxy](https://github.com/Thanabodin19/iotclass67/blob/main/assignment00/architecture.md#apache-kafka-rest-proxy) +* [kafka-connect](https://github.com/Thanabodin19/iotclass67/blob/main/assignment00/architecture.md#apache-kafka-connect) +* [mosquitto](https://github.com/Thanabodin19/iotclass67/blob/main/assignment00/architecture.md#eclipse-mosquitto) +* [mongo](https://github.com/Thanabodin19/iotclass67/blob/main/assignment00/architecture.md#mongodb) +* [grafana](https://github.com/Thanabodin19/iotclass67/blob/main/assignment00/architecture.md#grafana) +* [prometheus](https://github.com/Thanabodin19/iotclass67/blob/main/assignment00/architecture.md#prometheus) + +> [!NOTE] +> เมื่อรันคำสั่ง รอจนกว่า terminal จะแสดง +`kafka-connect Kafka Connect listener HTTP state: 000 (waiting for 200)` ถึงจะรัน `start-service #2` ต่อไป +## start-service #2 +```bash +sh start_2iot_processor.sh +``` +#### service +* [iot-processor](https://github.com/Thanabodin19/iotclass67/blob/main/assignment00/architecture.md#iot-processor) -## Output +> [!NOTE] +> รอจนกว่า iot-processor ขึ้น initialize ถึงจะรัน `start-service #3` ต่อไป -- [ ] IoT Sensor - Dashboards - Grafana -- [ ] UI for Apache Ka -- [ ] Mongo Expr -- [ ] Node Expor -- [ ] Prometheus Time Series Collection and Processing Ser -- [ ] Prometheus Pushgateway -- [ ] ZooNavigator +> [!WARNING] +> ถ้าขึ้น `iot-processor Shutdown complete` ให้ restart iot-processor +## start-service #3 +```bash +sh start_3iot_sensor.sh +``` +#### service +* [iot_sensor](https://github.com/Thanabodin19/iotclass67/blob/main/assignment00/architecture.md#iot-sensor) -### IoT Sensor - Dashboards - Grafana URL -### UI for Apache Kafka + -### ZooNavigator \ No newline at end of file diff --git a/assignment03/01-ingest.md b/assignment03/01-ingest.md index 172e63c..4689bc8 100644 --- a/assignment03/01-ingest.md +++ b/assignment03/01-ingest.md @@ -1 +1,15 @@ -# MQTT +# Ingest and store real-time data from IoT sensors +>> อธิบาย 3 ส่วนนี้ สร้างมาได้อย่างไร + +## iot-sensor-1 +>> คืออะไร +IoT sensor 1 เป็น sensor ที่ถูกจําลองด้วยไมโครเซอร์วิสที่ใช้ใน Spring Boot (ผ่านไลบรารี Eclipse Paho MQTT) ที่ถูกติดตั้งอยู่บนเซิฟเวอร์ โดยจะส่งข้อมูล telemetry ไปยังโบรกเกอร์ Eclipse Mosquitto ข้อมูลที่ถูกจำลองนี้ generate ค่า ทุกอย่างภายใน payload มาจาก Callable โดยจะถูกสร้างขึ้นทุกวินาทีและ มี payload ในรูปแบบที่สร้างขึ้นให้ตรงกัน + +## iot-sensor-2 +>> คืออะไร +เป็น sensor ที่ถูกจําลองด้วยไมโครเซอร์วิสที่ใช้ใน Spring Boot (ผ่านไลบรารี Eclipse Paho MQTT)เช่นเดียวกันกับ sensor 1 เพียงแต่ติดตั้งอยู่ในเครื่องของคนในทีม + +## iot-sensor-3-10 +>> คืออะไร +เซ็นเซอร์ 3 จะเป็นค่าจริงจาก sensor ที่เป็นการนำค่าที่อ่านได้จาก Cucumber RS ของกลุ่มตนเอง แตกต่างจาก iot-sensor-1, iot-sensor-2 ที่เป็นการ Mock-up แล้วส่งผ่าน MQTT เพื่อนำมาแสดง และ Payload ต่างๆก็ถูกตั้งให้อยู่ใน format เดียวกัน แล้ว เพื่อการรำไปใช้ต่อได้ในทุกๆ sensor +Sensor 4 - 10 นั้น จะเป็นค่าจริงเช่นเดียวกัน แต่จะเป็นค่าจาก จาก sensor ของกลุ่มอื่น ที่เป็นการนำค่าที่อ่านได้จาก Cucumber RS ของแต่ละกลุ่ม ที่ส่งผ่าน MQTT มาให้เช่นเดียวกัน เพื่อนำมาแสดง และ Payload ต่างๆก็ถูกตั้งให้อยู่ใน format เดียวกัน แล้ว เพื่อการแสดงผ่าน Cucumber แบบแสดงผลพร้อมกัน 10 sensor diff --git a/assignment04/01-iot-sensor.md b/assignment04/01-iot-sensor.md index bd9bcf6..151b566 100644 --- a/assignment04/01-iot-sensor.md +++ b/assignment04/01-iot-sensor.md @@ -1,13 +1,316 @@ -# Ingest and store real-time data from IoT sensors. - -## MQTT Topic - - -## MQTT Payload - - -## ESP32 - -```cpp - -``` \ No newline at end of file +# Ingest and store real-time data from IoT sensors. + +## MQTT Topic +ในโปรเจกต์วิชา Iot นี้ใช้ MQTT เป็นโปรโตคอลที่ใช้สำหรับการสื่อสารข้อมูลระหว่าง CUCUMBER RS (ESP32) กับ MQTT Broker โดยมี MQTT Topic ชื่อว่า `iot-frames` สำหรับการส่งข้อมูลจากเซ็นเซอร์ไปยัง Broker ดังนี้ + +```python +client.publish("iot-frames", jsonBuffer); +``` +นี่คือ Topic ที่ใช้เพื่อส่งในช่องเดียวกัน + +## MQTT Payload +ข้อมูลที่ส่งไปยัง MQTT Broker จะอยู่ในรูปแบบ JSON Payload ซึ่งประกอบไปด้วยข้อมูลของเซ็นเซอร์ต่าง ๆ และ timestamp +เช่น กรณีของ Sensor 3 จะต้องส่ง payload ให้อยู่ตามรูปแบบ Pattern ดังนี้ + +```json + doc["id"] = "43245253"; + doc["name"] = "iot_sensor_3"; + doc["place_id"] = "32347983"; + doc["date"] = NTP.getTimeDateString(time(NULL), "%Y-%m-%dT%H:%M:%S"); + doc["timestamp"] = epochTime; + doc["payload"]["temperature"] = temperature; + doc["payload"]["humidity"] = humidity; + doc["payload"]["pressure"] = p; + doc["payload"]["luminosity"] = ldrValue; +``` +- ตัวเวลาจะดึงจาก NTP server และจะได้มาในรูปแบบประมาณนี้ "date": "2024-07-15T10:30:00Z", +- payload นี้มีความสำคัญเมื่อต้องส่งค่าไปให้ทาง Server โดย Mosquito จะจัดการทุกอย่างและ นำข้อมูลไปส่งขึ้น Grafrana +## ESP32 +- โค้ดสำหรับ CUCUMBER +```cpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// WiFi credentials + +const char* ssid = "TP-Link_CA30"; +const char* password = "29451760"; + + +// MQTT Broker settings + +const char* mqtt_server = "172.16.46.37"; + +// 3 +const int mqtt_port = 1883; +const char* mqtt_user = "iot-frames-3"; // Replace with your MQTT username if needed +const char* mqtt_password = "piN5S6"; // Replace with your MQTT password if needed + +const PROGMEM char* ntpServer = "172.16.46.37"; + +#define NTP_TIMEOUT 5000 + +// MQTT Client +WiFiClient espClient; +PubSubClient client(espClient); + +// Sensors +Adafruit_BMP280 bmp; +SensirionI2CSht4x sht4x; +Adafruit_HTS221 hts; +Adafruit_MPU6050 mpu; + +// LED pin +const int ledPin = 2; + +// NeoPixel +#define PIN 18 // กำหนดขาให้เชื่อมต่อกับ NeoPixel +#define NUMPIXELS 1 // จำนวนของ NeoPixel + +Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); + +// ldr GPIO 5 +const int ldrPin = 5; // GPIO5 for LDR + +// MQTT callback function only one sensor +void callback(char* topic, byte* payload, unsigned int length) { + // Serial.print("Message arrived ["); + // Serial.print(topic); + // Serial.print("] "); + + String message; + for (int i = 0; i < length; i++) { + message += (char)payload[i]; + } + // Serial.println(message); + + // Parse JSON + StaticJsonDocument<200> doc; + DeserializationError error = deserializeJson(doc, message); + + if (error) { + Serial.print("deserializeJson() failed: "); + Serial.println(error.f_str()); + return; + } + + const char* sensor_name = doc["name"]; + + // Check if the sensor name is "iot_sensor_3" + if (strcmp(sensor_name, "iot_sensor_3") == 0) { + // Serial.print("----------------------------Filtered message: "); + Serial.println(message); + } +} + +void setup_wifi() { + delay(10); + // Define the desired IP address and subnet mask + IPAddress ip(172, 16, 46, 31); // Desired IP address + IPAddress gateway(172, 16, 46, 1); // Default gateway IP address + IPAddress subnet(255, 255, 255, 0); // Subnet mask + + // Connect to Wi-Fi network with specified IP configuration + Serial.println(); + Serial.print("Connecting to "); + Serial.println(ssid); + + WiFi.config(ip, gateway, subnet); // Set static IP configuration + + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); +} + +void reconnect() { + while (!client.connected()) { + Serial.print("Attempting MQTT connection..."); + setStatusLED(2); // กำลังเชื่อมต่อกับ MQTT YELLOW สลับ GREEN 5วิ + delay(1000); + setStatusLED(2); // กำลังเชื่อมต่อกับ MQTT YELLOW สลับ GREEN 5วิ + delay(1000); + if (client.connect("iot_sensor_3", mqtt_user, mqtt_password)) { + Serial.println("connected"); + setStatusLED(5); // MQTT เชื่อมต่อแล้ว GREEN + client.subscribe("iot-frames"); + delay(2000); // 5000 + } else { + Serial.print("failed, rc="); + Serial.print(client.state()); + Serial.println(" try again in 5 seconds"); + delay(5000); // 5000 + } + } +} + +// RGB +void setupRGB() { + pixels.begin(); // Initialize NeoPixel + pixels.setBrightness(50); // กำหนดความสว่างของ NeoPixel +} +void setRGBColor(uint8_t red, uint8_t green, uint8_t blue) { + pixels.clear(); // ล้างสีเก่า + pixels.setPixelColor(0, pixels.Color(red, green, blue)); // ตั้งค่าสีใหม่ + pixels.show(); // แสดงสีที่เลือก +} +// G R B +void setStatusLED(int status) { + switch (status) { + case 0: // ยังไม่ได้เชื่อมต่อ Wi-Fi และ MQTT + setRGBColor(0, 255, 0); // สีแดง + break; + case 1: // เชื่อมต่อ Wi-Fi ได้ + setRGBColor(255, 0, 0); // สีเขียว + delay(500); + break; + case 2: // ระหว่างเชื่อมต่อ MQTT + setRGBColor(255, 255, 0); // สีเหลือง + break; + case 3: // ระหว่างส่งข้อมูล + setRGBColor(255, 255, 255); // สีขาว + delay(1000); + setRGBColor(0, 0, 0); // ปิดไฟกระพริบ + delay(500); + break; + case 4: // ระหว่าง + setRGBColor(151, 252, 0); // สี + delay(1000); + setRGBColor(0, 0, 0); // ปิดไฟกระพริบ + delay(500); + break; + case 5: // ระหว่าง + setRGBColor(0, 0, 0); // สี + delay(1000); + setRGBColor(0, 0, 0); // ปิดไฟกระพริบ + delay(500); + break; + } +} + +void setupHardware() { + Wire.begin(41, 40, 100000); + if (bmp.begin(0x76)) { + Serial.println("BMP280 sensor ready"); + } + + sht4x.begin(Wire, 0x44); + Serial.println("SHT4x sensor initialized"); + + // if (hts.begin_I2C(0x5F)) { + // Serial.println("HTS221 sensor ready"); + // } else { + // Serial.println("HTS221 sensor NOT ready!!!"); + // } + + if (mpu.begin(0x68)) { + Serial.println("MPU6050 sensor ready"); + } + + pinMode(ledPin, OUTPUT); + digitalWrite(ledPin, HIGH); + + pinMode(ldrPin, INPUT); // prepare LDR + +} + +void setup() { + Serial.begin(115200); + setupHardware(); + setStatusLED(0); ////////// + setup_wifi(); + setupRGB(); + setStatusLED(1); // Wi-Fi เชื่อมต่อแล้ว GREEN + + client.setServer(mqtt_server, mqtt_port); + client.setCallback(callback); + + NTP.setTimeZone(TZ_Asia_Bangkok); + NTP.setInterval(600); + NTP.setNTPTimeout(NTP_TIMEOUT); + NTP.begin(ntpServer); + + Serial.println("Starting!!!"); +} + +unsigned long Get_Epoch_Time(){ + time_t now; + struct tm timeinfo; + if (!getLocalTime(&timeinfo)) { + return 0; + } + time(&now); + return now; +} + +void loop() { + if (WiFi.status() != WL_CONNECTED) { + setStatusLED(0); // แสดงสถานะ Wi-Fi หลุดด้วยสีแดง + setup_wifi(); // พยายามเชื่อมต่อ Wi-Fi ใหม่ + } else { + if (!client.connected()) { + reconnect(); + } + client.loop(); + + static uint32_t prev_millis = 0; + const size_t capacity = JSON_OBJECT_SIZE(6) + 200; + StaticJsonDocument doc; + + if (millis() - prev_millis > 5000) { + prev_millis = millis(); + + float p = bmp.readPressure(); + int ldrValue = analogRead(ldrPin); + float temperature, humidity; + int16_t error = sht4x.measureHighPrecision(temperature, humidity); + + if (error) { + Serial.print("Error trying to execute measureHighPrecision(): "); + Serial.println(error); + } else { + unsigned long epochTime = Get_Epoch_Time(); + + doc["id"] = "43245253"; + doc["name"] = "iot_sensor_3"; + doc["place_id"] = "32347983"; + doc["date"] = NTP.getTimeDateString(time(NULL), "%Y-%m-%dT%H:%M:%S"); + doc["timestamp"] = epochTime; + doc["payload"]["temperature"] = temperature; + doc["payload"]["humidity"] = humidity; + doc["payload"]["pressure"] = p; + doc["payload"]["luminosity"] = ldrValue; + + char jsonBuffer[capacity]; + serializeJson(doc, jsonBuffer); + + setStatusLED(3); // กำลังส่งข้อมูล + client.publish("iot-frames", jsonBuffer); + } + delay(2000); + } + } +} + + +``` +- Flow chart จากโค้ด Cucumber +![alt text](image-2.png) diff --git a/assignment04/image-1.png b/assignment04/image-1.png new file mode 100644 index 0000000..4776a9c Binary files /dev/null and b/assignment04/image-1.png differ diff --git a/assignment04/image-2.png b/assignment04/image-2.png new file mode 100644 index 0000000..2d294aa Binary files /dev/null and b/assignment04/image-2.png differ diff --git a/assignment04/image.png b/assignment04/image.png new file mode 100644 index 0000000..8308552 Binary files /dev/null and b/assignment04/image.png differ diff --git a/assignment05/01-analyze.md b/assignment05/01-analyze.md index 360f890..bdf4388 100644 --- a/assignment05/01-analyze.md +++ b/assignment05/01-analyze.md @@ -1,2 +1,259 @@ # Analyze and make aggregations. +>> อธิบาย ส่วนวิเคราห์ข้อมูล สามส่วนนี้ ใช้หลักการอย่างไร ทำแล้วได้อะไร + +## Aggregate Metrics By Sensor Processor +โปรเซสเซอร์ Aggregate Metrics By Sensor Processor ทำหน้าที่รวมข้อมูลจากเซ็นเซอร์โดยอิงตาม Sensor ID โดยใช้หน้าต่างเวลาแบบหมุน (sliding window) ขนาด 5 นาที เพื่อคำนวณค่าเฉลี่ยของพารามิเตอร์ต่างๆ เช่น อุณหภูมิ ความชื้น ความดันอากาศ และความส่องสว่าง +ในกระบวนการนี้ โปรเซสเซอร์จะสร้าง schema model ที่เหมาะสม และจำเป็นต้องกำหนดค่า SerDe (Serializer/Deserializer) ที่ถูกต้องเพื่อแปลงข้อมูลในรูปแบบที่สามารถจัดเก็บและดึงข้อมูลจาก Kafka ได้อย่างมีประสิทธิภาพ +```java +@Component +public class AggregateMetricsBySensorProcessor { + + private static final Logger logger = LoggerFactory.getLogger(AggregateMetricsBySensorProcessor.class); + + private final static int WINDOW_SIZE_IN_MINUTES = 5; + private final static String WINDOW_STORE_NAME = "aggregate-metrics-by-sensor-tmp"; + + /** + * Agg Metrics Sensor Topic Output + */ + @Value("${kafka.topic.aggregate-metrics-sensor}") + private String aggMetricsSensorOutput; + + /** + * + * @param stream + */ + public void process(KStream stream) { + buildAggregateMetricsBySensor(stream) + .to(aggMetricsSensorOutput, Produced.with(String(), new SensorAggregateMetricsSensorSerde())); + } + + /** + * Build Aggregate Metrics By Sensor Stream + * + * @param stream + * @return + */ + private KStream buildAggregateMetricsBySensor(KStream stream) { + return stream + .map((key, val) -> new KeyValue<>(val.getId(), val)) + .groupByKey(Grouped.with(String(), new SensorDataSerde())) + .windowedBy(TimeWindows.of(Duration.ofMinutes(WINDOW_SIZE_IN_MINUTES)).grace(Duration.ofMillis(0))) + .aggregate(SensorAggregateSensorMetricsDTO::new, + (String k, SensorDataDTO v, SensorAggregateSensorMetricsDTO va) -> aggregateData(v, va), + buildWindowPersistentStore() + ) + .suppress(Suppressed.untilWindowCloses(unbounded())) + .toStream() + .map((key, value) -> KeyValue.pair(key.key(), value)); + } + + /** + * Build Window Persistent Store + * + * @return + */ + private Materialized> buildWindowPersistentStore() { + return Materialized + .>as(WINDOW_STORE_NAME) + .withKeySerde(String()) + .withValueSerde(new SensorAggregateMetricsSensorSerde()); + } + + /** + * Aggregate Data + * + * @param v + * @param va + * @return + */ + private SensorAggregateSensorMetricsDTO aggregateData(final SensorDataDTO v, final SensorAggregateSensorMetricsDTO va) { + // Sensor Data + va.setId(v.getId()); + // Sensor Data + va.setId(v.getId()); + va.setName(v.getName()); + // Start Agg + if (va.getStartAgg() == null) { + final Date startAggAt = new Date(); + va.setStartAgg(startAggAt); + va.setStartAggTm(startAggAt.getTime()); + } + va.setCountMeasures(va.getCountMeasures() + 1); + // Temperature + va.setSumTemperature(va.getSumTemperature() + v.getPayload().getTemperature()); + va.setAvgTemperature(va.getSumTemperature() / va.getCountMeasures()); // Humidity + // Humidity + va.setSumHumidity(va.getSumHumidity() + v.getPayload().getHumidity()); + va.setAvgHumidity(va.getSumHumidity() / va.getCountMeasures()); // Luminosity + // Luminosity + va.setSumLuminosity(va.getSumLuminosity() + v.getPayload().getLuminosity()); + va.setAvgLuminosity(va.getSumLuminosity() / va.getCountMeasures()); // Pressure + // Pressure + va.setSumPressure(va.getSumPressure() + v.getPayload().getPressure()); + va.setAvgPressure(va.getSumPressure() / va.getCountMeasures()); + + // End Agg + final Date endAggAt = new Date(); + va.setEndAgg(endAggAt); + va.setEndAggTm(endAggAt.getTime()); + return va; + } + +} +``` +เมื่อหน้าต่างเวลาปิดลง (เมื่อครบ 5 นาที) ระบบจะสร้างบันทึกข้อมูลที่ประกอบไปด้วยการวัดผลรวมประมาณ 300 ค่า สำหรับเซ็นเซอร์แต่ละตัว โดยคำนวณค่าเฉลี่ยของแต่ละพารามิเตอร์ จากนั้นข้อมูลเหล่านี้จะถูกบันทึกลงในหัวข้อ iot-aggregate-metrics-by-sensor บน Kafka เพื่อใช้ในกระบวนการต่อไป +## Aggregate Metrics By Place Processor +โปรเซสเซอร์ Aggregate Metrics By Place Processor ทำหน้าที่รวมข้อมูลในลักษณะเดียวกับ Aggregate Metrics By Sensor Processor แต่ในกรณีนี้ การคำนวณค่าเฉลี่ยจะอ้างอิงจาก สถานที่ (Place ID) แทนที่จะเป็นเซ็นเซอร์แต่ละตัว +```java +@Component +public class AggregateMetricsByPlaceProcessor { + + private static final Logger logger = LoggerFactory.getLogger(AggregateMetricsByPlaceProcessor.class); + + private final static int WINDOW_SIZE_IN_MINUTES = 5; + private final static String WINDOW_STORE_NAME = "aggregate-metrics-by-place-tmp"; + + /** + * Agg Metrics Place Topic Output + */ + @Value("${kafka.topic.aggregate-metrics-place}") + private String aggMetricsPlaceOutput; + + /** + * + * @param stream + */ + public void process(KStream stream) { + buildAggregateMetrics(stream) + .to(aggMetricsPlaceOutput, Produced.with(String(), new SensorAggregateMetricsPlaceSerde())); + } + + /** + * Build Aggregate Metrics Stream + * + * @param stream + * @return + */ + private KStream buildAggregateMetrics(KStream stream) { + return stream + .map((key, val) -> new KeyValue<>(val.getPlaceId(), val)) + .groupByKey(Grouped.with(String(), new SensorDataSerde())) + .windowedBy(TimeWindows.of(Duration.ofMinutes(WINDOW_SIZE_IN_MINUTES)).grace(Duration.ofMillis(0))) + .aggregate(SensorAggregatePlaceMetricsDTO::new, + (String k, SensorDataDTO v, SensorAggregatePlaceMetricsDTO va) -> aggregateData(v, va), + buildWindowPersistentStore() + ) + .suppress(Suppressed.untilWindowCloses(unbounded())) + .toStream() + .map((key, value) -> KeyValue.pair(key.key(), value)); + } + + /** + * Build Window Persistent Store + * + * @return + */ + private Materialized> buildWindowPersistentStore() { + return Materialized + .>as(WINDOW_STORE_NAME) + .withKeySerde(String()) + .withValueSerde(new SensorAggregateMetricsPlaceSerde()); + } + + /** + * Aggregate Data + * + * @param v + * @param va + * @return + */ + private SensorAggregatePlaceMetricsDTO aggregateData(final SensorDataDTO v, final SensorAggregatePlaceMetricsDTO va) { + va.setPlaceId(v.getId()); + // Start Agg + if (va.getStartAgg() == null) { + final Date startAggAt = new Date(); + va.setStartAgg(startAggAt); + va.setStartAggTm(startAggAt.getTime()); + } + va.setCountMeasures(va.getCountMeasures() + 1); + // Temperature + va.setSumTemperature(va.getSumTemperature() + v.getPayload().getTemperature()); + va.setAvgTemperature(va.getSumTemperature() / va.getCountMeasures()); // Humidity + // Humidity + va.setSumHumidity(va.getSumHumidity() + v.getPayload().getHumidity()); + va.setAvgHumidity(va.getSumHumidity() / va.getCountMeasures()); // Luminosity + // Luminosity + va.setSumLuminosity(va.getSumLuminosity() + v.getPayload().getLuminosity()); + va.setAvgLuminosity(va.getSumLuminosity() / va.getCountMeasures()); // Pressure + // Pressure + va.setSumPressure(va.getSumPressure() + v.getPayload().getPressure()); + va.setAvgPressure(va.getSumPressure() / va.getCountMeasures()); + + // End Agg + final Date endAggAt = new Date(); + va.setEndAgg(endAggAt); + va.setEndAggTm(endAggAt.getTime()); + return va; + } + +} +``` +โปรเซสเซอร์จะสร้าง schema model ที่แตกต่างกัน ซึ่งมีการกำหนดค่า SerDe (Serializer/Deserializer) ที่เหมาะสม เพื่อให้สามารถบันทึกข้อมูลในรูปแบบ serialized และจัดเก็บลงในหัวข้อ iot-aggregate-metrics-place บน Kafka ได้อย่างถูกต้อง + +## Aggregate Metrics time series +โปรเซสเซอร์ Aggregate Metrics Time Series Processor มีจุดประสงค์ในการแปลงข้อมูลให้เหมาะสมกับการใช้งานใน Prometheus โดยใช้ชื่อเซ็นเซอร์, รหัสเซ็นเซอร์ และตัวระบุสถานที่เป็น มิติข้อมูล (dimensions) ในการจัดการข้อมูลเหล่านี้ +```java +@Component +public class MetricsTimeSeriesProcessor { + + private static final Logger logger = LoggerFactory.getLogger(MetricsTimeSeriesProcessor.class); + + private final static String SENSOR_TIME_SERIE_NAME = "sample_sensor_metric"; + private final static String SENSOR_TIME_SERIE_TYPE = "sensor"; + + /** + * Metrics Time Series + */ + @Value("${kafka.topic.metrics-time-series}") + private String metricTimeSeriesOutput; + + /** + * + * @param stream + */ + public void process(KStream stream) { + stream + .map((key, val) -> new KeyValue<>(val.getId(), buildSensorTimeSerieMetric(val))) + .to(metricTimeSeriesOutput, Produced.with(String(), new SensorTimeSerieMetricSerde())); + } + + /** + * Build Sensor Time Serie Meter + * + * @param sensorData + * @return + */ + private SensorTimeSerieMetricDTO buildSensorTimeSerieMetric(final SensorDataDTO sensorData) { + return SensorTimeSerieMetricDTO.builder() + .name(SENSOR_TIME_SERIE_NAME) + .timestamp(new Date().getTime()) + .type(SENSOR_TIME_SERIE_TYPE) + .dimensions(SensorTimeSerieMetricDimensionsDTO.builder() + .placeId(sensorData.getPlaceId()) + .sensorId(sensorData.getId()) + .sensorName(sensorData.getName()) + .build()) + .values(SensorTimeSerieMetricValuesDTO.builder() + .humidity((double) sensorData.getPayload().getHumidity()) + .luminosity((double) sensorData.getPayload().getLuminosity()) + .pressure((double) sensorData.getPayload().getPressure()) + .temperature((double) sensorData.getPayload().getTemperature()) + .build()) + .build(); + } + +} +``` +ข้อมูลที่ได้จะอยู่ในรูปแบบที่สามารถนำไปแสดงผลเป็น กราฟลำดับเวลา (time-series) บน Grafana ได้ โดยบันทึกเหล่านี้จะถูกจัดเก็บในหัวข้อ iot-metrics-time-series ซึ่ง Prometheus จะเข้ามาดึงข้อมูลจากหัวข้อนี้เพื่อนำไปใช้ในการตรวจสอบและวิเคราะห์เมตริกต่างๆ ในระบบ diff --git a/assignment06/01-storedata.md b/assignment06/01-storedata.md index 4c17659..75c97b7 100644 --- a/assignment06/01-storedata.md +++ b/assignment06/01-storedata.md @@ -1,2 +1,99 @@ # Store data. +## Kafka to MongoDB +### Overview +ขั้นตอนนี้มีจุดประสงค์ในการย้ายข้อมูลที่ประมวลผลแล้วจากหัวข้อ Kafka ไปยังคอลเลกชันที่เกี่ยวข้องใน MongoDB เพื่อให้สามารถดูและวิเคราะห์ข้อมูลผ่านเครื่องมือ เช่น MongoDB-Express ได้ในภายหลัง + +ได้มีการตั้งค่า **MongoDBSinkConnector** จำนวน 3 อินสแตนซ์ โดยแต่ละอินสแตนซ์จะดึงข้อมูลจากหัวข้อ Kafka และนำข้อมูลไปจัดเก็บในคอลเลกชันที่กำหนดไว้ใน MongoDB ดังนี้: + +1. ย้ายระเบียนต้นฉบับจากหัวข้อ `iot-frames` ไปยังคอลเล็กชัน `iot_frames` ในฐานข้อมูล `iot` +2. ย้ายข้อมูลจากหัวข้อ `iot-aggregate-metrics-sensor` ไปยังคอลเล็กชัน `iot_aggregate_metrics_sensor` +3. ย้ายข้อมูลรวมตามสถานที่จากหัวข้อ `iot-aggregate-metrics-place` ไปยังคอลเล็กชัน `iot_aggregate_metrics_place` + +### Configuration + +#### 1. MongoDB Sink Connector for `iot-frames` +ตัวเชื่อมต่อนี้จะย้ายข้อมูลจากหัวข้อ `iot-frames` ไปยังคอลเล็กชัน `iot_frames` ใน MongoDB โดยใช้ข้อมูลในรูปแบบ JSON และไม่จำเป็นต้องใช้ schema (เช่น Avro หรือ JSON-Schema) เพื่อลดความซับซ้อนของกระบวนการ + +```json +{ + "name":"iot-frames-mongodb-sink", + "config":{ + "connector.class":"com.mongodb.kafka.connect.MongoSinkConnector", + "tasks.max":1, + "topics":"iot-frames", + "connection.uri":"mongodb://devroot:devroot@mongo:27017", + "database":"iot", + "collection":"iot_frames", + "value.converter": "org.apache.kafka.connect.json.JsonConverter", + "key.converter": "org.apache.kafka.connect.json.JsonConverter", + "value.converter.schemas.enable": false, + "key.converter.schemas.enable":false + } +} +``` +#### 2. MongoDB Sink Connector for `iot-aggregate-metrics-sensor` +ตัวเชื่อมต่อนี้จะย้ายข้อมูลเมตริกที่รวมตามเซ็นเซอร์จากหัวข้อ `iot-aggregate-metrics-sensor` ไปยังคอลเล็กชัน `iot_aggregate_metrics_sensor` ใน MongoDB +```json +{ + "name":"iot-aggregate-metrics-sensor-mongodb-sink", + "config":{ + "connector.class":"com.mongodb.kafka.connect.MongoSinkConnector", + "tasks.max":1, + "topics":"iot-aggregate-metrics-sensor", + "connection.uri":"mongodb://devroot:devroot@mongo:27017", + "database":"iot", + "collection":"iot_aggregate_metrics_sensor", + "value.converter": "org.apache.kafka.connect.json.JsonConverter", + "key.converter": "org.apache.kafka.connect.storage.StringConverter", + "value.converter.schemas.enable": false, + "key.converter.schemas.enable": false + } +} +``` +#### 3. MongoDB Sink Connector for `iot-aggregate-metrics-place` +ตัวเชื่อมต่อนี้จะย้ายข้อมูลเมตริกที่รวมตามสถานที่จากหัวข้อ `iot-aggregate-metrics-place` ไปยังคอลเล็กชัน `iot_aggregate_metrics_place` +```json +{ + "name":"iot-aggregate-metrics-place-mongodb-sink", + "config":{ + "connector.class":"com.mongodb.kafka.connect.MongoSinkConnector", + "tasks.max":1, + "topics":"iot-aggregate-metrics-place", + "connection.uri":"mongodb://devroot:devroot@mongo:27017", + "database":"iot", + "collection":"iot_aggregate_metrics_place", + "value.converter": "org.apache.kafka.connect.json.JsonConverter", + "key.converter": "org.apache.kafka.connect.storage.StringConverter", + "value.converter.schemas.enable": false, + "key.converter.schemas.enable": false + } +} +``` + +## Kafka to Prometheus +Prometheus ซึ่งจำเป็นต้องกำหนดค่าตัวเชื่อมต่อที่สามารถย้ายข้อมูลจาก หัวข้อ `iot-metric-time-series` ไปยังฐานข้อมูลได้ Prometheus จะได้รับข้อมูลผ่านระบบการสำรวจข้อมูล ดังนั้นตัวเชื่อมต่อนี้จึงเปิดใช้งานเซิร์ฟเวอร์ HTTP ที่ Prometheus สามารถค้นหาข้อมูลได้ + +ตัวเชื่อมต่อ `Kafka Connect Prometheus Metrics Sink` ช่วยให้ข้อมูลนี้พร้อมใช้งานสำหรับจุดสิ้นสุดที่ถูกขูดโดยเซิร์ฟเวอร์ Prometheus ตัวเชื่อมต่อยอมรับโครงสร้างและ JSON แบบไม่มีโครงร่างเป็นค่าของระเบียน Kafka + +```json +{ + "name" : "prometheus-connector-sink", + "config" : { + "topics":"iot-metrics-time-series", + "connector.class" : "io.confluent.connect.prometheus.PrometheusMetricsSinkConnector", + "tasks.max" : "1", + "confluent.topic.bootstrap.servers":"kafka:9092", + "prometheus.scrape.url": "http://0.0.0.0:8084/iot-metrics-time-series", + "prometheus.listener.url": "http://0.0.0.0:8084/iot-metrics-time-series", + "value.converter": "org.apache.kafka.connect.json.JsonConverter", + "key.converter": "org.apache.kafka.connect.json.JsonConverter", + "value.converter.schemas.enable": false, + "key.converter.schemas.enable":false, + "reporter.bootstrap.servers": "kafka:9092", + "reporter.result.topic.replication.factor": "1", + "reporter.error.topic.replication.factor": "1", + "behavior.on.error": "log" + } +} +``` diff --git a/assignment07/01-visualization.md b/assignment07/01-visualization.md index 20a1822..3272d5b 100644 --- a/assignment07/01-visualization.md +++ b/assignment07/01-visualization.md @@ -1,2 +1,172 @@ -# Data Visualization. - +# Data Visualization. + +>> นำข้อมูลอะไรมาแสดงในส่วนของ Visualization บ้าง + +การสร้าง Dashboard สำหรับ Visualization โดยใช้ Grafana และ FlowCharting คือการนำข้อมูลจากเซ็นเซอร์มาวิเคราะห์และแสดงผลในรูปแบบแผนผังสถานที่ผ่าน FlowCharting ซึ่งเป็น Plugin ของ Grafana ที่ช่วยให้ผู้ใช้สามารถสร้างและปรับแต่งแผนผัง (Flowchart) เพื่อแสดงสถานะหรือข้อมูลต่างๆ ให้สามารถเข้าใจง่าย สำหรับการสร้างแผนผังสถานที่ที่ต้องการ ผู้ใช้สามารถสร้างแผนผังนั้นใน Draw.io ซึ่งเป็นเครื่องมือสำหรับการออกแบบ Diagram จากนั้นนำแผนผังที่สร้างเสร็จแล้วมาใช้ใน Grafana ผ่าน FlowCharting Plugin เพื่อเชื่อมต่อกับข้อมูลเซ็นเซอร์ แล้วนำมา แสดงผลข้อมูลลงบน Dashboard ซึ่งจะช่วยให้ผู้ใช้สามารถติดตามสถานะและข้อมูลต่างๆของแต่ละ sensor ได้อย่างมีประสิทธิภาพและเข้าใจง่าย + +## งานที่ได้ทำ +จากงานได้ใช้เซ็นเซอร์ทั้งหมด 10 ตัว เพื่อมาแสดงข้อมูล อุณหภูมิ (Temperature), ความชื้น (Humidity), ความสว่าง (Luminosity), และ ความดัน (Pressure) โดยมีทั้งรูปแบบ Guage, Stat และ Graph และยังสามารถเก็บข้อมูลเป็น time series data บน Prometeus ได้อีกด้วย +![My Image](pic/all.png) + +### Humidity + +![My Image](pic/humidity.png) +เหตุผลที่ได้เลือกใช้เป็น Stat Chart ในการแสดงค่า Humidity + +เนื่องจาก: + +1. Stat Chart แสดงข้อมูลที่ชัดเจนและทันที แสดงค่าเดียวที่สำคัญ เช่น ค่าปัจจุบัน ความชื้นสูงสุด หรือต่ำสุด ทำให้สามารถเข้าใจสถานะได้อย่างรวดเร็ว +2. Stat chart เหมาะสำหรับการเน้นข้อมูลที่มีความสำคัญและต้องการการตรวจสอบบ่อยครั้ง เนื่องจากความชื้นมีค่าความเปลี่ยนแปลงอยู่ตลอดเวลา +3. Stat Chart สามารถเห็นได้ชัดมากที่สุดเมื่อข้อมูลมีความเปลี่ยนแปลงไปมากเพื่อสังเกตุความผิดปกติของความชื้น ทำให้สามารถวิเคราะห์แนวโน้มของสภาพอากาศได้ + + + +### Temperature + +![My Image](pic/temp.png) + +เหตุผลที่ได้เลือกการเลือกใช้ Gauge chart ในการแสดงค่า Temperature + +เนื่องจาก: + +1. Gauge chart เป็นแผนภูมิที่เหมือนหน้าปัดของเครื่องมือวัด ทำให้ผู้ใช้สามารถเห็นค่าปัจจุบันของอุณหภูมิได้อย่างชัดเจนและเข้าใจได้ง่ายโดยไม่ต้องตีความเยอะ + +2. Gauge chart มีลักษณะเหมือนมาตรวัดที่เราคุ้นเคย เช่น มาตรวัดความเร็วหรือมาตรวัดอุณหภูมิในรถยนต์ ทำให้ผู้ใช้สามารถตีความสถานะของข้อมูลได้ทันที + +3. Gauge chart สามารถตั้งค่าเกณฑ์เพื่อแบ่งช่วงอุณหภูมิเป็นโซนต่างๆ เช่น โซนเย็น โซนปกติ โซนร้อน หรือโซนอันตราย ทำให้ผู้ใช้ทราบสถานะของอุณหภูมิ + + +### Luminosity + +![My Image](pic/lum.png) +เหตุผลที่ได้เลือกการเลือกใช้ Bar chart ในการแสดงค่า luminosity + +เนื่องจาก: + +1. Bar chart สามารถเปรียบเทียบค่าได้ชัดเจน เหมาะสำหรับการแสดงค่า luminosity จากหลายแหล่งหรือช่วงเวลา เพราะสามารถเปรียบเทียบค่าความสว่างได้อย่างชัดเจนระหว่าง sensor แต่ละตัว + +2. ความยาวของแท่งกราฟใน Bar chart ทำให้สามารถมองเห็นค่าความสว่างได้ง่าย โดยไม่จำเป็นต้องใช้การตีความซับซ้อน ถ้าเยอะแสดงว่าสว่างมาก ถ้าน้อยแสดงว่าสว่างน้อย + +3. Bar chart สามารถใช้ในการแสดงการเปลี่ยนแปลงของค่า Luminosity ในระยะยาว ทำให้สามารถวิเคราะห์แนวโน้มได้อย่างชัดเจน + +### Pressure + +![My Image](pic/pre.png) + +เหตุผลที่ได้เลือกใช้เป็น Stat Chart ในการแสดงค่า Pressure + +เนื่องจาก: + +1. Stat Chart ช่วยเน้นไปที่ค่าความดันที่สำคัญในขณะนั้น ซึ่งเป็นประโยชน์ในสถานการณ์ที่ต้องการติดตามความผิดปกติหรือความเสี่ยงทันที + +2. Stat Chart เน้นการตรวจสอบของการเปลี่ยนแปลงอย่างรวดเร็ว เช่น หากค่าความดันเกินจากช่วงปกติ สามารถสังเกตได้ทันทีผ่าน Stat Chart ที่แสดงค่าเดียวอย่างชัดเจน + +3. Stat Chart ไม่มีความซับซ้อนของข้อมูล จึงไม่จำเป็นต้องแสดงการเปลี่ยนแปลงเชิงลึก จึงให้ความกระชับและเข้าใจง่าย + +นอกจากนี้ได้แสดงผลอุณหภูมิบนแผนผังบ้านผ่าน FlowCharting (Plugin ของ Grafana) สำหรับการจำลองเป็น smart home ช่วยให้คุณสามารถตรวจสอบและติดตามสภาพแวดล้อมภายในบ้านได้อย่างมีประสิทธิภาพ +![My Image](pic/afterRule.png) + +โดยการแสดงผลบนแผนผังบ้าน สามารถใช้การเปลี่ยนสีของเซ็นเซอร์บนแผนผังเพื่อแสดงสถานะต่าง ๆ เช่น หากอุณหภูมิในห้องใดห้องหนึ่งสูงหรือต่ำเกินไป แผนผังจะแสดงสีที่แตกต่างกัน โดยเมื่ออุณหภูมิสูงกว่าที่กำหนด sensor จะเปลี่ยนเป็นสีแดง เพื่อแสดงถึงความร้อน และเมื่ออุณหภูมิต่ำเกินไป sensor จะเปลี่ยนเป็นสี น้ำเงิน เพื่อแสดงถึงความเย็น เป็นการแจ้งเตือนให้ทราบถึงความผิดปกติที่เกิดขึ้นภายในบ้าน วิธีนี้ช่วยให้ผู้ใช้งานสามารถรับรู้และดำเนินการแก้ไขได้ทัน ทำให้ smart home นี้มีความปลอดภัยและประหยัดพลังงานมากขึ้น + +# Start grafana +## Mount volumn for grafana + +ในการติดตั้ง FlowCharting plugin สำหรับ Grafana ขั้นตอนแรกที่สำคัญคือการ mount volume เพื่อจัดเก็บ plugin นี้อย่างถาวร การ mount volume จะช่วยให้ข้อมูลและ plugin ที่ติดตั้งอยู่ในพื้นที่เก็บข้อมูลนี้สามารถเข้าถึงได้อย่างต่อเนื่องแม้ในกรณีที่ระบบต้องรีสตาร์ทหรือมีการอัปเดต + + +ใช้โค้ดด้านล่างนี้ในไฟล์ `docker-compose.yml` + +```yaml +grafana: + image: grafana/grafana:latest-ubuntu + container_name: grafana + user: "0" + volumes: + - ./grafana/data:/var/lib/grafana + - ./grafana/dashboards:/etc/grafana/provisioning/dashboards + - ./grafana/datasources:/etc/grafana/provisioning/datasources + environment: + - GF_SECURITY_ADMIN_USER=${ADMIN_USER:-admin} + - GF_SECURITY_ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin} + - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-worldmap-panel,grafana-piechart-panel + - GF_USERS_ALLOW_SIGN_UP=false + - GF_SECURITY_ANGULAR_SUPPORT_ENABLED=True + - GF_FEATURE_TOGGLES_ANGULARDEPRECATIONUI=FALSE + restart: unless-stopped + links: + - prometheus + ports: + - "8085:3000" + +``` + +## Install flowcharting on grafana + +### Step 1 +เข้าไปที่ https://github.com/skyfrank/grafana-flowcharting/releases/tag/v1.0.0e + +### Step 2 +Download Assets: +agenty-flowcharting-panel-1.0.0e.231214594-SNAPSHOT.zip + +### Step 3 +เข้า Folder plugins แล้วเก็บไฟล์ zip ไว้ใน Folder +```yaml +cd iot_event_streaming_architechture/grafana/data/plugins/src +``` +### Step 4 +extract unzip ไฟล์ zip ที่ได้จากขั้นตอนที่ 2 + +```yaml +unzip agenty-flowcharting-panel-1.0.0e.231214594-SNAPSHOT.zip -d ../grafana-flowcharting +``` +### Step 5 +เมื่อ extract ไฟล์เรียบร้อยแล้ว จะต้อง restart grafana ใหม่ +```yaml +docker compose restart grafana +``` +### Step 6 +เข้าไปที่ grafana เพื่อดูว่ามี plugin ชื่อ FlowCharting ติดตั้งแล้วหรือยังเพื่อตรวจสอบว่าติดตั้งเรียบร้อยแล้ว + + + + + +## Using flowcharting on grafana + +ในตัวอย่างนี้จะบอกอุณหภูมิที่ sensor ทั้งหมด 10 ตัวตรวจจับได้จากโซนต่างๆของบ้าน 1 ชั้นเพื่อจำลองเป็น smart home + +### Step 1 +กด Add ในหน้า grafana dashboard โดยเลือกเป็น flowcharting เพื่อสร้าง widget แปลนบ้าน + +### Step 2 +กด edit เพื่อเข้าไปแก้ไข draw.io เพื่อเอารูปแปลนบ้าน 1 ชั้นมาใส่ ทำให้อยู่ในรูปแบบนี้ + +![My Image](pic/drawio.png) + +- เพิ่มวงรีที่มี Text ภายในเพื่อเป็นตัวแสดงถึงอุณหภูมิ ณ บริเวณ zone นั้นๆ +- เพิ่ม block รวมถึงตั้งชื่อโซนนั้นๆเพื่อเป็นการบอกว่าเซ็นเซอร์ตัวไหนอยู่โซนไหน + + +### Step 3 +เป็นการ query ข้อมูล Time Series data จาก Prometheus โดยจะเลือกเป็น sensor อุณหภูมิ โดยตั้งlabel เป็น sensor_name แล้วเลือก sensor ตัวที่ต้องการ แล้วกด run queries + +![My Image](pic/query.png) + +### Step 4 +เป็นการ set up rule เพื่อเป็นตัวแจ้งเตือนว่าอุณหภูมิอยู่ในช่วงไหน + +![My Image](pic/rule.png) + +### Step 5 +เมื่อทำเสร็จครบแล้วจะสามารถ save และ apply ได้ภาพตามนี้ + +![My Image](pic/afterRule.png) diff --git a/assignment07/pic/afterRule.png b/assignment07/pic/afterRule.png new file mode 100644 index 0000000..beee38e Binary files /dev/null and b/assignment07/pic/afterRule.png differ diff --git a/assignment07/pic/all.png b/assignment07/pic/all.png new file mode 100644 index 0000000..ec1a3e0 Binary files /dev/null and b/assignment07/pic/all.png differ diff --git a/assignment07/pic/drawio.png b/assignment07/pic/drawio.png new file mode 100644 index 0000000..cf49c45 Binary files /dev/null and b/assignment07/pic/drawio.png differ diff --git a/assignment07/pic/grafana1.png b/assignment07/pic/grafana1.png new file mode 100644 index 0000000..042a0bf Binary files /dev/null and b/assignment07/pic/grafana1.png differ diff --git a/assignment07/pic/grafana2.png b/assignment07/pic/grafana2.png new file mode 100644 index 0000000..1390aff Binary files /dev/null and b/assignment07/pic/grafana2.png differ diff --git a/assignment07/pic/grafana3.png b/assignment07/pic/grafana3.png new file mode 100644 index 0000000..ca35294 Binary files /dev/null and b/assignment07/pic/grafana3.png differ diff --git a/assignment07/pic/humidity.png b/assignment07/pic/humidity.png new file mode 100644 index 0000000..07db4f7 Binary files /dev/null and b/assignment07/pic/humidity.png differ diff --git a/assignment07/pic/lum.png b/assignment07/pic/lum.png new file mode 100644 index 0000000..a650078 Binary files /dev/null and b/assignment07/pic/lum.png differ diff --git a/assignment07/pic/pre.png b/assignment07/pic/pre.png new file mode 100644 index 0000000..2b0336d Binary files /dev/null and b/assignment07/pic/pre.png differ diff --git a/assignment07/pic/query.png b/assignment07/pic/query.png new file mode 100644 index 0000000..aff121a Binary files /dev/null and b/assignment07/pic/query.png differ diff --git a/assignment07/pic/rule.png b/assignment07/pic/rule.png new file mode 100644 index 0000000..69a1437 Binary files /dev/null and b/assignment07/pic/rule.png differ diff --git a/assignment07/pic/temp.png b/assignment07/pic/temp.png new file mode 100644 index 0000000..102ea83 Binary files /dev/null and b/assignment07/pic/temp.png differ diff --git a/assignment08/01-used-technology.md b/assignment08/01-used-technology.md index 126d919..b93e291 100644 --- a/assignment08/01-used-technology.md +++ b/assignment08/01-used-technology.md @@ -1 +1,25 @@ -# Used technology \ No newline at end of file +# Used technology +>> สรุปว่าเราใช้ technology stack อะไรบ้างในโครงการนี้ ทำ link ไปยังแต่ละส่วน + + +- [ ] Ubuntu Server Installation +- [ ] Docker Containerization +- [ ] Kafka Streams. +- [ ] Spring Kafka. +- [ ] Eclipse Paho MQTT Client. +- [ ] Kafka Connect. +- [ ] Kafka Rest Proxy +- [ ] Spring Boot 2.3.3 / Apache Maven 3.6.3. +- [ ] Spring Boot Starter Actuator. +- [ ] Micrometer Registry Prometheus. +- [ ] lombok. +- [ ] Jackson. +- [ ] NodeExporter (Exporter for machine metrics). +- [ ] Prometheus. +- [ ] Grafana. +- [ ] Eclipse Mosquitto. +- [ ] MongoDB. +- [ ] Mongo DB Express (Web-based MongoDB admin interface, written with Node.js and express). +- [ ] Cadvisor (Analyzes resource usage and performance characteristics of running containers). +- [ ] kafka-exporter (Kafka exporter for Prometheus). +