chore(i18n): sync translations with latest source changes (chunk 3/3, 156 changes)

pull/571/head
localizeflow[bot] 7 months ago
parent 62b97ae7f6
commit 3438878756

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

@ -0,0 +1,830 @@
{
"1-getting-started/README.md": {
"original_hash": "e2b1b891b08ef7633d285547fbe73290",
"translation_date": "2025-08-25T21:54:04+00:00",
"source_file": "1-getting-started/README.md",
"language_code": "fa"
},
"1-getting-started/lessons/1-introduction-to-iot/README.md": {
"original_hash": "9bae08314d8487cb76ddf3d8797e1544",
"translation_date": "2025-08-25T22:11:50+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/README.md",
"language_code": "fa"
},
"1-getting-started/lessons/1-introduction-to-iot/assignment.md": {
"original_hash": "7ef1cec2d27b086032d46ab1958f3e99",
"translation_date": "2025-08-25T22:13:12+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/assignment.md",
"language_code": "fa"
},
"1-getting-started/lessons/1-introduction-to-iot/pi.md": {
"original_hash": "8ff0d0a1d29832bb896b9c103b69a452",
"translation_date": "2025-08-25T22:16:51+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/pi.md",
"language_code": "fa"
},
"1-getting-started/lessons/1-introduction-to-iot/virtual-device.md": {
"original_hash": "52b4de6144b2efdced7797a5339d6035",
"translation_date": "2025-08-25T22:14:33+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/virtual-device.md",
"language_code": "fa"
},
"1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md": {
"original_hash": "a4f0c166010e31fd7b6ca20bc88dec6d",
"translation_date": "2025-08-25T22:18:49+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md",
"language_code": "fa"
},
"1-getting-started/lessons/2-deeper-dive/README.md": {
"original_hash": "9dd7f645ad1c6f20b72fee512987f772",
"translation_date": "2025-08-25T22:22:16+00:00",
"source_file": "1-getting-started/lessons/2-deeper-dive/README.md",
"language_code": "fa"
},
"1-getting-started/lessons/2-deeper-dive/assignment.md": {
"original_hash": "750bd75866471141f857240219084767",
"translation_date": "2025-08-25T22:24:17+00:00",
"source_file": "1-getting-started/lessons/2-deeper-dive/assignment.md",
"language_code": "fa"
},
"1-getting-started/lessons/3-sensors-and-actuators/README.md": {
"original_hash": "e9ee00eb5fc55922a73762acc542166b",
"translation_date": "2025-08-25T22:04:38+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/README.md",
"language_code": "fa"
},
"1-getting-started/lessons/3-sensors-and-actuators/assignment.md": {
"original_hash": "c5a568320b1159394108544807895337",
"translation_date": "2025-08-25T22:05:52+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/assignment.md",
"language_code": "fa"
},
"1-getting-started/lessons/3-sensors-and-actuators/pi-actuator.md": {
"original_hash": "4db8a3879a53490513571df2f6cf7641",
"translation_date": "2025-08-25T22:06:33+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/pi-actuator.md",
"language_code": "fa"
},
"1-getting-started/lessons/3-sensors-and-actuators/pi-sensor.md": {
"original_hash": "ea733bd0cdf2479e082373f765a08678",
"translation_date": "2025-08-25T22:02:28+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/pi-sensor.md",
"language_code": "fa"
},
"1-getting-started/lessons/3-sensors-and-actuators/virtual-device-actuator.md": {
"original_hash": "9c640f93263fd9adbfda920739e09feb",
"translation_date": "2025-08-25T22:08:14+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/virtual-device-actuator.md",
"language_code": "fa"
},
"1-getting-started/lessons/3-sensors-and-actuators/virtual-device-sensor.md": {
"original_hash": "11f10c6760fb8202cf368422702fdf70",
"translation_date": "2025-08-25T22:09:00+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/virtual-device-sensor.md",
"language_code": "fa"
},
"1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-actuator.md": {
"original_hash": "db44083b4dc6fb06eac83c4f16448940",
"translation_date": "2025-08-25T22:07:25+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-actuator.md",
"language_code": "fa"
},
"1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-sensor.md": {
"original_hash": "7f4ad0ef54f248b85b92187c94cf9dcb",
"translation_date": "2025-08-25T22:09:35+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-sensor.md",
"language_code": "fa"
},
"1-getting-started/lessons/4-connect-internet/README.md": {
"original_hash": "71b5040e0b3472f1c0949c9b55f224c0",
"translation_date": "2025-08-25T21:57:31+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/README.md",
"language_code": "fa"
},
"1-getting-started/lessons/4-connect-internet/assignment.md": {
"original_hash": "0d4033cdd7b5b5475c63770102e38480",
"translation_date": "2025-08-25T21:59:28+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/assignment.md",
"language_code": "fa"
},
"1-getting-started/lessons/4-connect-internet/single-board-computer-commands.md": {
"original_hash": "c527ce85d69b1a3875366ec61cbed8aa",
"translation_date": "2025-08-25T21:54:29+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/single-board-computer-commands.md",
"language_code": "fa"
},
"1-getting-started/lessons/4-connect-internet/single-board-computer-mqtt.md": {
"original_hash": "90fb93446e03c38f3c0e4009c2471906",
"translation_date": "2025-08-25T21:59:59+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/single-board-computer-mqtt.md",
"language_code": "fa"
},
"1-getting-started/lessons/4-connect-internet/single-board-computer-telemetry.md": {
"original_hash": "1226517aae5f5b6f904434670394c688",
"translation_date": "2025-08-25T21:54:54+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/single-board-computer-telemetry.md",
"language_code": "fa"
},
"1-getting-started/lessons/4-connect-internet/wio-terminal-commands.md": {
"original_hash": "6754c915dae64ba70fcd5e52c37f3adf",
"translation_date": "2025-08-25T22:01:49+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/wio-terminal-commands.md",
"language_code": "fa"
},
"1-getting-started/lessons/4-connect-internet/wio-terminal-mqtt.md": {
"original_hash": "d6faf0e8d3c2d6d20c0aef2a305dab18",
"translation_date": "2025-08-25T22:01:02+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/wio-terminal-mqtt.md",
"language_code": "fa"
},
"1-getting-started/lessons/4-connect-internet/wio-terminal-telemetry.md": {
"original_hash": "4bcc29fe2b65e56eada83d2476279227",
"translation_date": "2025-08-25T21:59:07+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/wio-terminal-telemetry.md",
"language_code": "fa"
},
"2-farm/README.md": {
"original_hash": "428bda82d9e6016ecea7c797564bf081",
"translation_date": "2025-08-25T21:15:11+00:00",
"source_file": "2-farm/README.md",
"language_code": "fa"
},
"2-farm/lessons/1-predict-plant-growth/README.md": {
"original_hash": "d105b44deae539165855c976dcdeca99",
"translation_date": "2025-08-25T21:17:19+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/README.md",
"language_code": "fa"
},
"2-farm/lessons/1-predict-plant-growth/assignment.md": {
"original_hash": "1e21b012c6685f8bf73e0e76cdca3347",
"translation_date": "2025-08-25T21:19:07+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/assignment.md",
"language_code": "fa"
},
"2-farm/lessons/1-predict-plant-growth/pi-temp.md": {
"original_hash": "7678f7c67b97ee52d5727496dcd7d346",
"translation_date": "2025-08-25T21:22:28+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/pi-temp.md",
"language_code": "fa"
},
"2-farm/lessons/1-predict-plant-growth/single-board-computer-temp-publish.md": {
"original_hash": "4efc74299e19f5d08f2f3f34451a11ba",
"translation_date": "2025-08-25T21:18:38+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/single-board-computer-temp-publish.md",
"language_code": "fa"
},
"2-farm/lessons/1-predict-plant-growth/virtual-device-temp.md": {
"original_hash": "70e5a428b607cd5a9a4f422c2a4df03d",
"translation_date": "2025-08-25T21:21:40+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/virtual-device-temp.md",
"language_code": "fa"
},
"2-farm/lessons/1-predict-plant-growth/wio-terminal-temp-publish.md": {
"original_hash": "df28cd649cd892bcce034e064913b2f3",
"translation_date": "2025-08-25T21:20:45+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/wio-terminal-temp-publish.md",
"language_code": "fa"
},
"2-farm/lessons/1-predict-plant-growth/wio-terminal-temp.md": {
"original_hash": "59263d094f20b302053888cd236880c3",
"translation_date": "2025-08-25T21:19:59+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/wio-terminal-temp.md",
"language_code": "fa"
},
"2-farm/lessons/2-detect-soil-moisture/README.md": {
"original_hash": "4fb20273d299dc8d07a8f06c9cd0cdd9",
"translation_date": "2025-08-25T21:38:12+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/README.md",
"language_code": "fa"
},
"2-farm/lessons/2-detect-soil-moisture/assignment.md": {
"original_hash": "506d21b544d5de47406c89ad496a21cd",
"translation_date": "2025-08-25T21:40:19+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/assignment.md",
"language_code": "fa"
},
"2-farm/lessons/2-detect-soil-moisture/pi-soil-moisture.md": {
"original_hash": "9d4d00a47d5d0f3e6ce42c0d1020064a",
"translation_date": "2025-08-25T21:41:50+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/pi-soil-moisture.md",
"language_code": "fa"
},
"2-farm/lessons/2-detect-soil-moisture/virtual-device-soil-moisture.md": {
"original_hash": "2bf65f162bcebd35fbcba5fd245afac4",
"translation_date": "2025-08-25T21:41:01+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/virtual-device-soil-moisture.md",
"language_code": "fa"
},
"2-farm/lessons/2-detect-soil-moisture/wio-terminal-soil-moisture.md": {
"original_hash": "0d55caa8c23d73635b7559102cd17b8a",
"translation_date": "2025-08-25T21:39:37+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/wio-terminal-soil-moisture.md",
"language_code": "fa"
},
"2-farm/lessons/3-automated-plant-watering/README.md": {
"original_hash": "f7bb24ba53fb627ddb38a8b24a05e594",
"translation_date": "2025-08-25T21:25:06+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/README.md",
"language_code": "fa"
},
"2-farm/lessons/3-automated-plant-watering/assignment.md": {
"original_hash": "ed0fbd6aed084bfba7d5e2f206968c50",
"translation_date": "2025-08-25T21:26:45+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/assignment.md",
"language_code": "fa"
},
"2-farm/lessons/3-automated-plant-watering/pi-relay.md": {
"original_hash": "66b81165e60f8f169bd52a401b6a0f8b",
"translation_date": "2025-08-25T21:28:17+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/pi-relay.md",
"language_code": "fa"
},
"2-farm/lessons/3-automated-plant-watering/virtual-device-relay.md": {
"original_hash": "f8f541ee945545017a51aaf309aa37c3",
"translation_date": "2025-08-25T21:29:02+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/virtual-device-relay.md",
"language_code": "fa"
},
"2-farm/lessons/3-automated-plant-watering/wio-terminal-relay.md": {
"original_hash": "f3c5d8afa2ef6a0b425ef8ff20615cb4",
"translation_date": "2025-08-25T21:27:30+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/wio-terminal-relay.md",
"language_code": "fa"
},
"2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md": {
"original_hash": "4d8e7a066d75b625e7a979c14157041d",
"translation_date": "2025-08-25T21:44:30+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md",
"language_code": "fa"
},
"2-farm/lessons/4-migrate-your-plant-to-the-cloud/assignment.md": {
"original_hash": "bfd35499bd68d7d740242bfea784bbeb",
"translation_date": "2025-08-25T21:46:21+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/assignment.md",
"language_code": "fa"
},
"2-farm/lessons/4-migrate-your-plant-to-the-cloud/single-board-computer-connect-hub.md": {
"original_hash": "3ac42e284a7222c0e83d2d43231a364f",
"translation_date": "2025-08-25T21:46:55+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/single-board-computer-connect-hub.md",
"language_code": "fa"
},
"2-farm/lessons/4-migrate-your-plant-to-the-cloud/wio-terminal-connect-hub.md": {
"original_hash": "28320305a35ea3bc59c41fe146a2e6ed",
"translation_date": "2025-08-25T21:47:57+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/wio-terminal-connect-hub.md",
"language_code": "fa"
},
"2-farm/lessons/5-migrate-application-to-the-cloud/README.md": {
"original_hash": "5f2d2f4a5a023c93ab34a0cc5b47c0c4",
"translation_date": "2025-08-25T21:32:18+00:00",
"source_file": "2-farm/lessons/5-migrate-application-to-the-cloud/README.md",
"language_code": "fa"
},
"2-farm/lessons/5-migrate-application-to-the-cloud/assignment.md": {
"original_hash": "c24b6e4d90501c9199f2ceb6a648a337",
"translation_date": "2025-08-25T21:34:18+00:00",
"source_file": "2-farm/lessons/5-migrate-application-to-the-cloud/assignment.md",
"language_code": "fa"
},
"2-farm/lessons/6-keep-your-plant-secure/README.md": {
"original_hash": "81c437c568eee1b0dda1f04e88150d37",
"translation_date": "2025-08-25T21:50:44+00:00",
"source_file": "2-farm/lessons/6-keep-your-plant-secure/README.md",
"language_code": "fa"
},
"2-farm/lessons/6-keep-your-plant-secure/assignment.md": {
"original_hash": "34010c663d96d5f419eda6ac2366a78d",
"translation_date": "2025-08-25T21:52:23+00:00",
"source_file": "2-farm/lessons/6-keep-your-plant-secure/assignment.md",
"language_code": "fa"
},
"2-farm/lessons/6-keep-your-plant-secure/single-board-computer-x509.md": {
"original_hash": "9aea84bcc7520222b0e1c50469d62d6a",
"translation_date": "2025-08-25T21:52:54+00:00",
"source_file": "2-farm/lessons/6-keep-your-plant-secure/single-board-computer-x509.md",
"language_code": "fa"
},
"2-farm/lessons/6-keep-your-plant-secure/wio-terminal-x509.md": {
"original_hash": "8a74f789f3c1bf41a13c007190360c19",
"translation_date": "2025-08-25T21:53:14+00:00",
"source_file": "2-farm/lessons/6-keep-your-plant-secure/wio-terminal-x509.md",
"language_code": "fa"
},
"3-transport/README.md": {
"original_hash": "e978534a245b000725ed2a048f943213",
"translation_date": "2025-08-25T22:52:30+00:00",
"source_file": "3-transport/README.md",
"language_code": "fa"
},
"3-transport/lessons/1-location-tracking/README.md": {
"original_hash": "52ed2bd997d08040f79a1a6ef2bac958",
"translation_date": "2025-08-25T23:00:10+00:00",
"source_file": "3-transport/lessons/1-location-tracking/README.md",
"language_code": "fa"
},
"3-transport/lessons/1-location-tracking/assignment.md": {
"original_hash": "bded364fc06ce37d7a76aed3be1ba73a",
"translation_date": "2025-08-25T23:01:20+00:00",
"source_file": "3-transport/lessons/1-location-tracking/assignment.md",
"language_code": "fa"
},
"3-transport/lessons/1-location-tracking/pi-gps-sensor.md": {
"original_hash": "3b2448c7ab4e9673e77e35a50c5e350d",
"translation_date": "2025-08-25T23:02:36+00:00",
"source_file": "3-transport/lessons/1-location-tracking/pi-gps-sensor.md",
"language_code": "fa"
},
"3-transport/lessons/1-location-tracking/single-board-computer-gps-decode.md": {
"original_hash": "cbb8c285bc64c5192fae3368fb5077d2",
"translation_date": "2025-08-25T23:01:48+00:00",
"source_file": "3-transport/lessons/1-location-tracking/single-board-computer-gps-decode.md",
"language_code": "fa"
},
"3-transport/lessons/1-location-tracking/virtual-device-gps-sensor.md": {
"original_hash": "64f18a8f8aaa1fef5e7320e0992d8b3a",
"translation_date": "2025-08-25T22:58:03+00:00",
"source_file": "3-transport/lessons/1-location-tracking/virtual-device-gps-sensor.md",
"language_code": "fa"
},
"3-transport/lessons/1-location-tracking/wio-terminal-gps-decode.md": {
"original_hash": "fbbcf96a9b63ccd661db98bbf854bb06",
"translation_date": "2025-08-25T23:03:10+00:00",
"source_file": "3-transport/lessons/1-location-tracking/wio-terminal-gps-decode.md",
"language_code": "fa"
},
"3-transport/lessons/1-location-tracking/wio-terminal-gps-sensor.md": {
"original_hash": "da6ae0a795cf06be33d23ca5b8493fc8",
"translation_date": "2025-08-25T22:57:07+00:00",
"source_file": "3-transport/lessons/1-location-tracking/wio-terminal-gps-sensor.md",
"language_code": "fa"
},
"3-transport/lessons/2-store-location-data/README.md": {
"original_hash": "e345843ccfeb7261d81500d19c64d476",
"translation_date": "2025-08-25T23:08:22+00:00",
"source_file": "3-transport/lessons/2-store-location-data/README.md",
"language_code": "fa"
},
"3-transport/lessons/2-store-location-data/assignment.md": {
"original_hash": "b2e0a965723082b068f735aec0faf3f6",
"translation_date": "2025-08-25T23:10:18+00:00",
"source_file": "3-transport/lessons/2-store-location-data/assignment.md",
"language_code": "fa"
},
"3-transport/lessons/3-visualize-location-data/README.md": {
"original_hash": "9095c61445c2bca7245ef9b59a186a11",
"translation_date": "2025-08-25T23:04:51+00:00",
"source_file": "3-transport/lessons/3-visualize-location-data/README.md",
"language_code": "fa"
},
"3-transport/lessons/3-visualize-location-data/assignment.md": {
"original_hash": "0ccdc1faa676a485c4c6ecbddb9f9067",
"translation_date": "2025-08-25T23:05:57+00:00",
"source_file": "3-transport/lessons/3-visualize-location-data/assignment.md",
"language_code": "fa"
},
"3-transport/lessons/4-geofences/README.md": {
"original_hash": "078ae664c7b686bf069545e9a5fc95b2",
"translation_date": "2025-08-25T22:54:49+00:00",
"source_file": "3-transport/lessons/4-geofences/README.md",
"language_code": "fa"
},
"3-transport/lessons/4-geofences/assignment.md": {
"original_hash": "5cb65a6ec4387ed177e145347e8e308e",
"translation_date": "2025-08-25T22:56:28+00:00",
"source_file": "3-transport/lessons/4-geofences/assignment.md",
"language_code": "fa"
},
"4-manufacturing/README.md": {
"original_hash": "3764e089adf2d5801272bc0895f8498b",
"translation_date": "2025-08-25T20:52:14+00:00",
"source_file": "4-manufacturing/README.md",
"language_code": "fa"
},
"4-manufacturing/lessons/1-train-fruit-detector/README.md": {
"original_hash": "f5e63c916d2dd97d58be12aaf76bd9f1",
"translation_date": "2025-08-25T20:54:34+00:00",
"source_file": "4-manufacturing/lessons/1-train-fruit-detector/README.md",
"language_code": "fa"
},
"4-manufacturing/lessons/1-train-fruit-detector/assignment.md": {
"original_hash": "e74eb2fc7cc3b81916b52e957802f182",
"translation_date": "2025-08-25T20:55:54+00:00",
"source_file": "4-manufacturing/lessons/1-train-fruit-detector/assignment.md",
"language_code": "fa"
},
"4-manufacturing/lessons/2-check-fruit-from-device/README.md": {
"original_hash": "557f4ee96b752e0651d2e6e74aa6bd14",
"translation_date": "2025-08-25T20:57:01+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/README.md",
"language_code": "fa"
},
"4-manufacturing/lessons/2-check-fruit-from-device/assignment.md": {
"original_hash": "022e21f8629b721424c1de25195fff67",
"translation_date": "2025-08-25T20:57:46+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/assignment.md",
"language_code": "fa"
},
"4-manufacturing/lessons/2-check-fruit-from-device/pi-camera.md": {
"original_hash": "c677667095f6133eee418c7e53615d05",
"translation_date": "2025-08-25T20:58:35+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/pi-camera.md",
"language_code": "fa"
},
"4-manufacturing/lessons/2-check-fruit-from-device/single-board-computer-classify-image.md": {
"original_hash": "e5896207b304ce1abaf065b8acc0cc79",
"translation_date": "2025-08-25T20:59:35+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/single-board-computer-classify-image.md",
"language_code": "fa"
},
"4-manufacturing/lessons/2-check-fruit-from-device/virtual-device-camera.md": {
"original_hash": "3ba7150ffc4a6999f6c3cfb4906ec7df",
"translation_date": "2025-08-25T21:01:28+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/virtual-device-camera.md",
"language_code": "fa"
},
"4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-camera.md": {
"original_hash": "160be8c0f558687f6686dca64f10f739",
"translation_date": "2025-08-25T21:03:09+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-camera.md",
"language_code": "fa"
},
"4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-classify-image.md": {
"original_hash": "32a1f23e7834fbe7715da8c4ebb450b9",
"translation_date": "2025-08-25T21:00:30+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-classify-image.md",
"language_code": "fa"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/README.md": {
"original_hash": "2625af24587465c5547ae33d6cc000a5",
"translation_date": "2025-08-25T21:06:25+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/README.md",
"language_code": "fa"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/assignment.md": {
"original_hash": "cc7ad255517f5f618f9c8899e6ff6783",
"translation_date": "2025-08-25T21:08:03+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/assignment.md",
"language_code": "fa"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/single-board-computer.md": {
"original_hash": "50151d9f9dce2801348a93880ef16d86",
"translation_date": "2025-08-25T21:09:46+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/single-board-computer.md",
"language_code": "fa"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/vm-iotedge.md": {
"original_hash": "24dc783a600e20251211987b36370e93",
"translation_date": "2025-08-25T21:08:38+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/vm-iotedge.md",
"language_code": "fa"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/wio-terminal.md": {
"original_hash": "48ac21ec80329c930db7b84bd6b592ec",
"translation_date": "2025-08-25T21:09:16+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/wio-terminal.md",
"language_code": "fa"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/README.md": {
"original_hash": "f74f4ccb61f00e5f7e9f49c3ed416e36",
"translation_date": "2025-08-25T21:11:24+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/README.md",
"language_code": "fa"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/assignment.md": {
"original_hash": "1a85e50c33c38dcd2cde2a97d132f248",
"translation_date": "2025-08-25T21:12:41+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/assignment.md",
"language_code": "fa"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/pi-proximity.md": {
"original_hash": "6145a1d791731c8a9d0afd0a1bae5108",
"translation_date": "2025-08-25T21:13:16+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/pi-proximity.md",
"language_code": "fa"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/virtual-device-proximity.md": {
"original_hash": "7e9f05bdc50a40fd924b1d66934471bf",
"translation_date": "2025-08-25T21:14:39+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/virtual-device-proximity.md",
"language_code": "fa"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/wio-terminal-proximity.md": {
"original_hash": "288aebb0c59f7be1d2719b8f9660a313",
"translation_date": "2025-08-25T21:14:01+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/wio-terminal-proximity.md",
"language_code": "fa"
},
"5-retail/README.md": {
"original_hash": "22a1d6e49f2a689fe5bfa7802a7241fc",
"translation_date": "2025-08-25T20:43:34+00:00",
"source_file": "5-retail/README.md",
"language_code": "fa"
},
"5-retail/lessons/1-train-stock-detector/README.md": {
"original_hash": "8df310a42f902139a01417dacb1ffbef",
"translation_date": "2025-08-25T20:50:37+00:00",
"source_file": "5-retail/lessons/1-train-stock-detector/README.md",
"language_code": "fa"
},
"5-retail/lessons/1-train-stock-detector/assignment.md": {
"original_hash": "d93ee76fac4c2199973689ecd05baaf9",
"translation_date": "2025-08-25T20:51:44+00:00",
"source_file": "5-retail/lessons/1-train-stock-detector/assignment.md",
"language_code": "fa"
},
"5-retail/lessons/2-check-stock-device/README.md": {
"original_hash": "1c9e5fa8b7be726c75a97232b1e41c97",
"translation_date": "2025-08-25T20:45:29+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/README.md",
"language_code": "fa"
},
"5-retail/lessons/2-check-stock-device/assignment.md": {
"original_hash": "3cf7783991ec0ee4f6041223924894c7",
"translation_date": "2025-08-25T20:46:18+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/assignment.md",
"language_code": "fa"
},
"5-retail/lessons/2-check-stock-device/single-board-computer-count-stock.md": {
"original_hash": "9c4320311c0f2c1884a6a21265d98a51",
"translation_date": "2025-08-25T20:48:02+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/single-board-computer-count-stock.md",
"language_code": "fa"
},
"5-retail/lessons/2-check-stock-device/single-board-computer-object-detector.md": {
"original_hash": "a3fdfec1d1e2cb645ea11c2930b51299",
"translation_date": "2025-08-25T20:44:09+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/single-board-computer-object-detector.md",
"language_code": "fa"
},
"5-retail/lessons/2-check-stock-device/wio-terminal-count-stock.md": {
"original_hash": "0b2ae20b0fc8e73c9598dea937cac038",
"translation_date": "2025-08-25T20:46:52+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/wio-terminal-count-stock.md",
"language_code": "fa"
},
"5-retail/lessons/2-check-stock-device/wio-terminal-object-detector.md": {
"original_hash": "4cf1421420a6fab9ab4f2c391bd523b7",
"translation_date": "2025-08-25T20:48:55+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/wio-terminal-object-detector.md",
"language_code": "fa"
},
"6-consumer/README.md": {
"original_hash": "5de7dc1e2ddc402d415473bb795568d4",
"translation_date": "2025-08-25T22:24:41+00:00",
"source_file": "6-consumer/README.md",
"language_code": "fa"
},
"6-consumer/lessons/1-speech-recognition/README.md": {
"original_hash": "6d6aa1be033625d201a190fc9c5cbfb4",
"translation_date": "2025-08-25T22:43:50+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/README.md",
"language_code": "fa"
},
"6-consumer/lessons/1-speech-recognition/assignment.md": {
"original_hash": "5ae7654f519ae831179409dc8e528055",
"translation_date": "2025-08-25T22:44:57+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/assignment.md",
"language_code": "fa"
},
"6-consumer/lessons/1-speech-recognition/pi-audio.md": {
"original_hash": "0ac0afcfb40cb5970ef4cb74f01c32e9",
"translation_date": "2025-08-25T22:41:23+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/pi-audio.md",
"language_code": "fa"
},
"6-consumer/lessons/1-speech-recognition/pi-microphone.md": {
"original_hash": "7e45d884493c5222348b43fbc4481b6a",
"translation_date": "2025-08-25T22:49:53+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/pi-microphone.md",
"language_code": "fa"
},
"6-consumer/lessons/1-speech-recognition/pi-speech-to-text.md": {
"original_hash": "af249a24d4fe4f4de4806adbc3bc9d86",
"translation_date": "2025-08-25T22:51:24+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/pi-speech-to-text.md",
"language_code": "fa"
},
"6-consumer/lessons/1-speech-recognition/virtual-device-audio.md": {
"original_hash": "e4f2925acb211765889c3b51b9116ceb",
"translation_date": "2025-08-25T22:51:45+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/virtual-device-audio.md",
"language_code": "fa"
},
"6-consumer/lessons/1-speech-recognition/virtual-device-microphone.md": {
"original_hash": "7a65ee743f916276a2848b8a9491feb7",
"translation_date": "2025-08-25T22:52:02+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/virtual-device-microphone.md",
"language_code": "fa"
},
"6-consumer/lessons/1-speech-recognition/virtual-device-speech-to-text.md": {
"original_hash": "c0550b254b9ba2539baf1e6bb5fc05f8",
"translation_date": "2025-08-25T22:45:30+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/virtual-device-speech-to-text.md",
"language_code": "fa"
},
"6-consumer/lessons/1-speech-recognition/wio-terminal-audio.md": {
"original_hash": "2f336726b9410e97c3aaed76cc89b0d8",
"translation_date": "2025-08-25T22:47:13+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/wio-terminal-audio.md",
"language_code": "fa"
},
"6-consumer/lessons/1-speech-recognition/wio-terminal-microphone.md": {
"original_hash": "93d352de36526b8990e41dd538100324",
"translation_date": "2025-08-25T22:50:39+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/wio-terminal-microphone.md",
"language_code": "fa"
},
"6-consumer/lessons/1-speech-recognition/wio-terminal-speech-to-text.md": {
"original_hash": "3f92edf2975175577174910caca4a389",
"translation_date": "2025-08-25T22:48:52+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/wio-terminal-speech-to-text.md",
"language_code": "fa"
},
"6-consumer/lessons/2-language-understanding/README.md": {
"original_hash": "6f4ba69d77f16c4a5110623a96a215c3",
"translation_date": "2025-08-25T22:32:16+00:00",
"source_file": "6-consumer/lessons/2-language-understanding/README.md",
"language_code": "fa"
},
"6-consumer/lessons/2-language-understanding/assignment.md": {
"original_hash": "5a7262a0c48dfacdfe1ff91b20bf16fd",
"translation_date": "2025-08-25T22:33:59+00:00",
"source_file": "6-consumer/lessons/2-language-understanding/assignment.md",
"language_code": "fa"
},
"6-consumer/lessons/3-spoken-feedback/README.md": {
"original_hash": "b73fe10ec6b580fba2affb6f6e0a5c4d",
"translation_date": "2025-08-25T22:35:36+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/README.md",
"language_code": "fa"
},
"6-consumer/lessons/3-spoken-feedback/assignment.md": {
"original_hash": "da5d9360fe02fdcc1e91a725016c846d",
"translation_date": "2025-08-25T22:36:13+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/assignment.md",
"language_code": "fa"
},
"6-consumer/lessons/3-spoken-feedback/pi-text-to-speech.md": {
"original_hash": "606f3af1c78e3741e48ce77c31cea626",
"translation_date": "2025-08-25T22:38:03+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/pi-text-to-speech.md",
"language_code": "fa"
},
"6-consumer/lessons/3-spoken-feedback/single-board-computer-set-timer.md": {
"original_hash": "64ad4ddb4de81a18b7252e968f10b404",
"translation_date": "2025-08-25T22:34:32+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/single-board-computer-set-timer.md",
"language_code": "fa"
},
"6-consumer/lessons/3-spoken-feedback/virtual-device-text-to-speech.md": {
"original_hash": "7966848a1f870e4c42edb4db67b13c57",
"translation_date": "2025-08-25T22:40:36+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/virtual-device-text-to-speech.md",
"language_code": "fa"
},
"6-consumer/lessons/3-spoken-feedback/wio-terminal-set-timer.md": {
"original_hash": "012b69d57d898d670adf61304f42a137",
"translation_date": "2025-08-25T22:37:13+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/wio-terminal-set-timer.md",
"language_code": "fa"
},
"6-consumer/lessons/3-spoken-feedback/wio-terminal-text-to-speech.md": {
"original_hash": "a202fa5889790a3777bfc33dd9f4b459",
"translation_date": "2025-08-25T22:39:32+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/wio-terminal-text-to-speech.md",
"language_code": "fa"
},
"6-consumer/lessons/4-multiple-language-support/README.md": {
"original_hash": "c16de27b0074abe81d6a8bad5e5b1a6b",
"translation_date": "2025-08-25T22:27:07+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/README.md",
"language_code": "fa"
},
"6-consumer/lessons/4-multiple-language-support/assignment.md": {
"original_hash": "701f4a4466f9309b6e1d863077df0c06",
"translation_date": "2025-08-25T22:27:59+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/assignment.md",
"language_code": "fa"
},
"6-consumer/lessons/4-multiple-language-support/pi-translate-speech.md": {
"original_hash": "bbb5aa34221fe129dd3ce4d9ec33831a",
"translation_date": "2025-08-25T22:28:39+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/pi-translate-speech.md",
"language_code": "fa"
},
"6-consumer/lessons/4-multiple-language-support/virtual-device-translate-speech.md": {
"original_hash": "d620a470d9dd8614d99824832978360a",
"translation_date": "2025-08-25T22:29:37+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/virtual-device-translate-speech.md",
"language_code": "fa"
},
"6-consumer/lessons/4-multiple-language-support/wio-terminal-translate-speech.md": {
"original_hash": "5f6c164e349f8989959e02a90f37908d",
"translation_date": "2025-08-25T22:25:36+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/wio-terminal-translate-speech.md",
"language_code": "fa"
},
"CODE_OF_CONDUCT.md": {
"original_hash": "c06b12caf3c901eb3156e3dd5b0aea56",
"translation_date": "2025-08-25T20:40:40+00:00",
"source_file": "CODE_OF_CONDUCT.md",
"language_code": "fa"
},
"CONTRIBUTING.md": {
"original_hash": "d6f80293fa9c213283eac7e79b078671",
"translation_date": "2025-08-25T20:42:56+00:00",
"source_file": "CONTRIBUTING.md",
"language_code": "fa"
},
"README.md": {
"original_hash": "3fcc056cd3837e33d2bdf79f9c359fcf",
"translation_date": "2026-01-30T01:13:04+00:00",
"source_file": "README.md",
"language_code": "fa"
},
"SECURITY.md": {
"original_hash": "8587f83cfded1bfab99fda4022f4df89",
"translation_date": "2025-08-25T20:42:32+00:00",
"source_file": "SECURITY.md",
"language_code": "fa"
},
"SUPPORT.md": {
"original_hash": "cd89329575372232e59605f7a08ae0df",
"translation_date": "2025-08-25T20:39:30+00:00",
"source_file": "SUPPORT.md",
"language_code": "fa"
},
"TROUBLESHOOTING.md": {
"original_hash": "9713e21a309662f6fcb271b573d47848",
"translation_date": "2026-01-05T17:29:25+00:00",
"source_file": "TROUBLESHOOTING.md",
"language_code": "fa"
},
"attributions.md": {
"original_hash": "4506d33bbda7acc0ab20980172687090",
"translation_date": "2025-08-25T20:35:35+00:00",
"source_file": "attributions.md",
"language_code": "fa"
},
"clean-up.md": {
"original_hash": "5a94fbab1ba737e9bd6cc6c64f114fa0",
"translation_date": "2025-08-25T20:40:21+00:00",
"source_file": "clean-up.md",
"language_code": "fa"
},
"docs/_sidebar.md": {
"original_hash": "686f22febaa2b67aa03b738c0dc0bf9b",
"translation_date": "2025-08-25T21:53:40+00:00",
"source_file": "docs/_sidebar.md",
"language_code": "fa"
},
"docs/troubleshooting.md": {
"original_hash": "271dcd720357806934b2f0d94e19410e",
"translation_date": "2026-01-05T17:45:53+00:00",
"source_file": "docs/troubleshooting.md",
"language_code": "fa"
},
"for-teachers.md": {
"original_hash": "9fd36f5dc734203ee28b6cf2573e5eab",
"translation_date": "2025-08-25T20:39:54+00:00",
"source_file": "for-teachers.md",
"language_code": "fa"
},
"hardware.md": {
"original_hash": "3dce18fab38adf93ff30b8c221b1eec5",
"translation_date": "2025-08-25T20:41:41+00:00",
"source_file": "hardware.md",
"language_code": "fa"
},
"images/README.md": {
"original_hash": "50abd54997afa7e7a3fc7019379e49e3",
"translation_date": "2025-08-25T20:43:14+00:00",
"source_file": "images/README.md",
"language_code": "fa"
},
"lesson-template/README.md": {
"original_hash": "0494be70ad7fadd13a8c3d549c23e355",
"translation_date": "2025-08-25T23:10:38+00:00",
"source_file": "lesson-template/README.md",
"language_code": "fa"
},
"lesson-template/assignment.md": {
"original_hash": "b5f62ec256c7e43e771f0d3b4e1a9130",
"translation_date": "2025-08-25T23:10:54+00:00",
"source_file": "lesson-template/assignment.md",
"language_code": "fa"
},
"quiz-app/README.md": {
"original_hash": "2a459ea9177fb0508ca96068ae1009d2",
"translation_date": "2025-08-25T23:11:21+00:00",
"source_file": "quiz-app/README.md",
"language_code": "fa"
},
"recommended-learning-model.md": {
"original_hash": "012bbd19f13171be32ac9ba21d4186c2",
"translation_date": "2025-08-25T20:36:15+00:00",
"source_file": "recommended-learning-model.md",
"language_code": "fa"
}
}

@ -0,0 +1,830 @@
{
"1-getting-started/README.md": {
"original_hash": "e2b1b891b08ef7633d285547fbe73290",
"translation_date": "2025-08-26T23:09:05+00:00",
"source_file": "1-getting-started/README.md",
"language_code": "ur"
},
"1-getting-started/lessons/1-introduction-to-iot/README.md": {
"original_hash": "9bae08314d8487cb76ddf3d8797e1544",
"translation_date": "2025-08-26T23:33:53+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/README.md",
"language_code": "ur"
},
"1-getting-started/lessons/1-introduction-to-iot/assignment.md": {
"original_hash": "7ef1cec2d27b086032d46ab1958f3e99",
"translation_date": "2025-08-26T23:36:22+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/assignment.md",
"language_code": "ur"
},
"1-getting-started/lessons/1-introduction-to-iot/pi.md": {
"original_hash": "8ff0d0a1d29832bb896b9c103b69a452",
"translation_date": "2025-08-26T23:40:20+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/pi.md",
"language_code": "ur"
},
"1-getting-started/lessons/1-introduction-to-iot/virtual-device.md": {
"original_hash": "52b4de6144b2efdced7797a5339d6035",
"translation_date": "2025-08-26T23:37:36+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/virtual-device.md",
"language_code": "ur"
},
"1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md": {
"original_hash": "a4f0c166010e31fd7b6ca20bc88dec6d",
"translation_date": "2025-08-26T23:43:12+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md",
"language_code": "ur"
},
"1-getting-started/lessons/2-deeper-dive/README.md": {
"original_hash": "9dd7f645ad1c6f20b72fee512987f772",
"translation_date": "2025-08-26T23:47:27+00:00",
"source_file": "1-getting-started/lessons/2-deeper-dive/README.md",
"language_code": "ur"
},
"1-getting-started/lessons/2-deeper-dive/assignment.md": {
"original_hash": "750bd75866471141f857240219084767",
"translation_date": "2025-08-26T23:50:44+00:00",
"source_file": "1-getting-started/lessons/2-deeper-dive/assignment.md",
"language_code": "ur"
},
"1-getting-started/lessons/3-sensors-and-actuators/README.md": {
"original_hash": "e9ee00eb5fc55922a73762acc542166b",
"translation_date": "2025-08-26T23:24:00+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/README.md",
"language_code": "ur"
},
"1-getting-started/lessons/3-sensors-and-actuators/assignment.md": {
"original_hash": "c5a568320b1159394108544807895337",
"translation_date": "2025-08-26T23:26:06+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/assignment.md",
"language_code": "ur"
},
"1-getting-started/lessons/3-sensors-and-actuators/pi-actuator.md": {
"original_hash": "4db8a3879a53490513571df2f6cf7641",
"translation_date": "2025-08-26T23:26:57+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/pi-actuator.md",
"language_code": "ur"
},
"1-getting-started/lessons/3-sensors-and-actuators/pi-sensor.md": {
"original_hash": "ea733bd0cdf2479e082373f765a08678",
"translation_date": "2025-08-26T23:21:55+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/pi-sensor.md",
"language_code": "ur"
},
"1-getting-started/lessons/3-sensors-and-actuators/virtual-device-actuator.md": {
"original_hash": "9c640f93263fd9adbfda920739e09feb",
"translation_date": "2025-08-26T23:29:11+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/virtual-device-actuator.md",
"language_code": "ur"
},
"1-getting-started/lessons/3-sensors-and-actuators/virtual-device-sensor.md": {
"original_hash": "11f10c6760fb8202cf368422702fdf70",
"translation_date": "2025-08-26T23:30:15+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/virtual-device-sensor.md",
"language_code": "ur"
},
"1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-actuator.md": {
"original_hash": "db44083b4dc6fb06eac83c4f16448940",
"translation_date": "2025-08-26T23:28:10+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-actuator.md",
"language_code": "ur"
},
"1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-sensor.md": {
"original_hash": "7f4ad0ef54f248b85b92187c94cf9dcb",
"translation_date": "2025-08-26T23:31:16+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-sensor.md",
"language_code": "ur"
},
"1-getting-started/lessons/4-connect-internet/README.md": {
"original_hash": "71b5040e0b3472f1c0949c9b55f224c0",
"translation_date": "2025-08-26T23:14:16+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/README.md",
"language_code": "ur"
},
"1-getting-started/lessons/4-connect-internet/assignment.md": {
"original_hash": "0d4033cdd7b5b5475c63770102e38480",
"translation_date": "2025-08-26T23:18:18+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/assignment.md",
"language_code": "ur"
},
"1-getting-started/lessons/4-connect-internet/single-board-computer-commands.md": {
"original_hash": "c527ce85d69b1a3875366ec61cbed8aa",
"translation_date": "2025-08-26T23:09:42+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/single-board-computer-commands.md",
"language_code": "ur"
},
"1-getting-started/lessons/4-connect-internet/single-board-computer-mqtt.md": {
"original_hash": "90fb93446e03c38f3c0e4009c2471906",
"translation_date": "2025-08-26T23:18:59+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/single-board-computer-mqtt.md",
"language_code": "ur"
},
"1-getting-started/lessons/4-connect-internet/single-board-computer-telemetry.md": {
"original_hash": "1226517aae5f5b6f904434670394c688",
"translation_date": "2025-08-26T23:10:21+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/single-board-computer-telemetry.md",
"language_code": "ur"
},
"1-getting-started/lessons/4-connect-internet/wio-terminal-commands.md": {
"original_hash": "6754c915dae64ba70fcd5e52c37f3adf",
"translation_date": "2025-08-26T23:21:09+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/wio-terminal-commands.md",
"language_code": "ur"
},
"1-getting-started/lessons/4-connect-internet/wio-terminal-mqtt.md": {
"original_hash": "d6faf0e8d3c2d6d20c0aef2a305dab18",
"translation_date": "2025-08-26T23:20:01+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/wio-terminal-mqtt.md",
"language_code": "ur"
},
"1-getting-started/lessons/4-connect-internet/wio-terminal-telemetry.md": {
"original_hash": "4bcc29fe2b65e56eada83d2476279227",
"translation_date": "2025-08-26T23:17:45+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/wio-terminal-telemetry.md",
"language_code": "ur"
},
"2-farm/README.md": {
"original_hash": "428bda82d9e6016ecea7c797564bf081",
"translation_date": "2025-08-26T22:11:52+00:00",
"source_file": "2-farm/README.md",
"language_code": "ur"
},
"2-farm/lessons/1-predict-plant-growth/README.md": {
"original_hash": "d105b44deae539165855c976dcdeca99",
"translation_date": "2025-08-26T22:14:26+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/README.md",
"language_code": "ur"
},
"2-farm/lessons/1-predict-plant-growth/assignment.md": {
"original_hash": "1e21b012c6685f8bf73e0e76cdca3347",
"translation_date": "2025-08-26T22:16:53+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/assignment.md",
"language_code": "ur"
},
"2-farm/lessons/1-predict-plant-growth/pi-temp.md": {
"original_hash": "7678f7c67b97ee52d5727496dcd7d346",
"translation_date": "2025-08-26T22:21:23+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/pi-temp.md",
"language_code": "ur"
},
"2-farm/lessons/1-predict-plant-growth/single-board-computer-temp-publish.md": {
"original_hash": "4efc74299e19f5d08f2f3f34451a11ba",
"translation_date": "2025-08-26T22:16:12+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/single-board-computer-temp-publish.md",
"language_code": "ur"
},
"2-farm/lessons/1-predict-plant-growth/virtual-device-temp.md": {
"original_hash": "70e5a428b607cd5a9a4f422c2a4df03d",
"translation_date": "2025-08-26T22:19:58+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/virtual-device-temp.md",
"language_code": "ur"
},
"2-farm/lessons/1-predict-plant-growth/wio-terminal-temp-publish.md": {
"original_hash": "df28cd649cd892bcce034e064913b2f3",
"translation_date": "2025-08-26T22:18:50+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/wio-terminal-temp-publish.md",
"language_code": "ur"
},
"2-farm/lessons/1-predict-plant-growth/wio-terminal-temp.md": {
"original_hash": "59263d094f20b302053888cd236880c3",
"translation_date": "2025-08-26T22:17:52+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/wio-terminal-temp.md",
"language_code": "ur"
},
"2-farm/lessons/2-detect-soil-moisture/README.md": {
"original_hash": "4fb20273d299dc8d07a8f06c9cd0cdd9",
"translation_date": "2025-08-26T22:46:02+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/README.md",
"language_code": "ur"
},
"2-farm/lessons/2-detect-soil-moisture/assignment.md": {
"original_hash": "506d21b544d5de47406c89ad496a21cd",
"translation_date": "2025-08-26T22:50:50+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/assignment.md",
"language_code": "ur"
},
"2-farm/lessons/2-detect-soil-moisture/pi-soil-moisture.md": {
"original_hash": "9d4d00a47d5d0f3e6ce42c0d1020064a",
"translation_date": "2025-08-26T22:53:05+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/pi-soil-moisture.md",
"language_code": "ur"
},
"2-farm/lessons/2-detect-soil-moisture/virtual-device-soil-moisture.md": {
"original_hash": "2bf65f162bcebd35fbcba5fd245afac4",
"translation_date": "2025-08-26T22:51:51+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/virtual-device-soil-moisture.md",
"language_code": "ur"
},
"2-farm/lessons/2-detect-soil-moisture/wio-terminal-soil-moisture.md": {
"original_hash": "0d55caa8c23d73635b7559102cd17b8a",
"translation_date": "2025-08-26T22:49:16+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/wio-terminal-soil-moisture.md",
"language_code": "ur"
},
"2-farm/lessons/3-automated-plant-watering/README.md": {
"original_hash": "f7bb24ba53fb627ddb38a8b24a05e594",
"translation_date": "2025-08-26T22:24:01+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/README.md",
"language_code": "ur"
},
"2-farm/lessons/3-automated-plant-watering/assignment.md": {
"original_hash": "ed0fbd6aed084bfba7d5e2f206968c50",
"translation_date": "2025-08-26T22:27:51+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/assignment.md",
"language_code": "ur"
},
"2-farm/lessons/3-automated-plant-watering/pi-relay.md": {
"original_hash": "66b81165e60f8f169bd52a401b6a0f8b",
"translation_date": "2025-08-26T22:29:43+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/pi-relay.md",
"language_code": "ur"
},
"2-farm/lessons/3-automated-plant-watering/virtual-device-relay.md": {
"original_hash": "f8f541ee945545017a51aaf309aa37c3",
"translation_date": "2025-08-26T22:30:50+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/virtual-device-relay.md",
"language_code": "ur"
},
"2-farm/lessons/3-automated-plant-watering/wio-terminal-relay.md": {
"original_hash": "f3c5d8afa2ef6a0b425ef8ff20615cb4",
"translation_date": "2025-08-26T22:28:55+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/wio-terminal-relay.md",
"language_code": "ur"
},
"2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md": {
"original_hash": "4d8e7a066d75b625e7a979c14157041d",
"translation_date": "2025-08-26T22:56:15+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md",
"language_code": "ur"
},
"2-farm/lessons/4-migrate-your-plant-to-the-cloud/assignment.md": {
"original_hash": "bfd35499bd68d7d740242bfea784bbeb",
"translation_date": "2025-08-26T22:59:08+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/assignment.md",
"language_code": "ur"
},
"2-farm/lessons/4-migrate-your-plant-to-the-cloud/single-board-computer-connect-hub.md": {
"original_hash": "3ac42e284a7222c0e83d2d43231a364f",
"translation_date": "2025-08-26T22:59:53+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/single-board-computer-connect-hub.md",
"language_code": "ur"
},
"2-farm/lessons/4-migrate-your-plant-to-the-cloud/wio-terminal-connect-hub.md": {
"original_hash": "28320305a35ea3bc59c41fe146a2e6ed",
"translation_date": "2025-08-26T23:01:14+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/wio-terminal-connect-hub.md",
"language_code": "ur"
},
"2-farm/lessons/5-migrate-application-to-the-cloud/README.md": {
"original_hash": "5f2d2f4a5a023c93ab34a0cc5b47c0c4",
"translation_date": "2025-08-26T22:37:53+00:00",
"source_file": "2-farm/lessons/5-migrate-application-to-the-cloud/README.md",
"language_code": "ur"
},
"2-farm/lessons/5-migrate-application-to-the-cloud/assignment.md": {
"original_hash": "c24b6e4d90501c9199f2ceb6a648a337",
"translation_date": "2025-08-26T22:41:32+00:00",
"source_file": "2-farm/lessons/5-migrate-application-to-the-cloud/assignment.md",
"language_code": "ur"
},
"2-farm/lessons/6-keep-your-plant-secure/README.md": {
"original_hash": "81c437c568eee1b0dda1f04e88150d37",
"translation_date": "2025-08-26T23:04:10+00:00",
"source_file": "2-farm/lessons/6-keep-your-plant-secure/README.md",
"language_code": "ur"
},
"2-farm/lessons/6-keep-your-plant-secure/assignment.md": {
"original_hash": "34010c663d96d5f419eda6ac2366a78d",
"translation_date": "2025-08-26T23:06:51+00:00",
"source_file": "2-farm/lessons/6-keep-your-plant-secure/assignment.md",
"language_code": "ur"
},
"2-farm/lessons/6-keep-your-plant-secure/single-board-computer-x509.md": {
"original_hash": "9aea84bcc7520222b0e1c50469d62d6a",
"translation_date": "2025-08-26T23:07:30+00:00",
"source_file": "2-farm/lessons/6-keep-your-plant-secure/single-board-computer-x509.md",
"language_code": "ur"
},
"2-farm/lessons/6-keep-your-plant-secure/wio-terminal-x509.md": {
"original_hash": "8a74f789f3c1bf41a13c007190360c19",
"translation_date": "2025-08-26T23:08:01+00:00",
"source_file": "2-farm/lessons/6-keep-your-plant-secure/wio-terminal-x509.md",
"language_code": "ur"
},
"3-transport/README.md": {
"original_hash": "e978534a245b000725ed2a048f943213",
"translation_date": "2025-08-27T00:34:39+00:00",
"source_file": "3-transport/README.md",
"language_code": "ur"
},
"3-transport/lessons/1-location-tracking/README.md": {
"original_hash": "52ed2bd997d08040f79a1a6ef2bac958",
"translation_date": "2025-08-27T00:46:54+00:00",
"source_file": "3-transport/lessons/1-location-tracking/README.md",
"language_code": "ur"
},
"3-transport/lessons/1-location-tracking/assignment.md": {
"original_hash": "bded364fc06ce37d7a76aed3be1ba73a",
"translation_date": "2025-08-27T00:49:15+00:00",
"source_file": "3-transport/lessons/1-location-tracking/assignment.md",
"language_code": "ur"
},
"3-transport/lessons/1-location-tracking/pi-gps-sensor.md": {
"original_hash": "3b2448c7ab4e9673e77e35a50c5e350d",
"translation_date": "2025-08-27T00:50:57+00:00",
"source_file": "3-transport/lessons/1-location-tracking/pi-gps-sensor.md",
"language_code": "ur"
},
"3-transport/lessons/1-location-tracking/single-board-computer-gps-decode.md": {
"original_hash": "cbb8c285bc64c5192fae3368fb5077d2",
"translation_date": "2025-08-27T00:49:55+00:00",
"source_file": "3-transport/lessons/1-location-tracking/single-board-computer-gps-decode.md",
"language_code": "ur"
},
"3-transport/lessons/1-location-tracking/virtual-device-gps-sensor.md": {
"original_hash": "64f18a8f8aaa1fef5e7320e0992d8b3a",
"translation_date": "2025-08-27T00:43:47+00:00",
"source_file": "3-transport/lessons/1-location-tracking/virtual-device-gps-sensor.md",
"language_code": "ur"
},
"3-transport/lessons/1-location-tracking/wio-terminal-gps-decode.md": {
"original_hash": "fbbcf96a9b63ccd661db98bbf854bb06",
"translation_date": "2025-08-27T00:51:59+00:00",
"source_file": "3-transport/lessons/1-location-tracking/wio-terminal-gps-decode.md",
"language_code": "ur"
},
"3-transport/lessons/1-location-tracking/wio-terminal-gps-sensor.md": {
"original_hash": "da6ae0a795cf06be33d23ca5b8493fc8",
"translation_date": "2025-08-27T00:42:29+00:00",
"source_file": "3-transport/lessons/1-location-tracking/wio-terminal-gps-sensor.md",
"language_code": "ur"
},
"3-transport/lessons/2-store-location-data/README.md": {
"original_hash": "e345843ccfeb7261d81500d19c64d476",
"translation_date": "2025-08-27T00:59:52+00:00",
"source_file": "3-transport/lessons/2-store-location-data/README.md",
"language_code": "ur"
},
"3-transport/lessons/2-store-location-data/assignment.md": {
"original_hash": "b2e0a965723082b068f735aec0faf3f6",
"translation_date": "2025-08-27T01:03:18+00:00",
"source_file": "3-transport/lessons/2-store-location-data/assignment.md",
"language_code": "ur"
},
"3-transport/lessons/3-visualize-location-data/README.md": {
"original_hash": "9095c61445c2bca7245ef9b59a186a11",
"translation_date": "2025-08-27T00:54:10+00:00",
"source_file": "3-transport/lessons/3-visualize-location-data/README.md",
"language_code": "ur"
},
"3-transport/lessons/3-visualize-location-data/assignment.md": {
"original_hash": "0ccdc1faa676a485c4c6ecbddb9f9067",
"translation_date": "2025-08-27T00:56:43+00:00",
"source_file": "3-transport/lessons/3-visualize-location-data/assignment.md",
"language_code": "ur"
},
"3-transport/lessons/4-geofences/README.md": {
"original_hash": "078ae664c7b686bf069545e9a5fc95b2",
"translation_date": "2025-08-27T00:38:37+00:00",
"source_file": "3-transport/lessons/4-geofences/README.md",
"language_code": "ur"
},
"3-transport/lessons/4-geofences/assignment.md": {
"original_hash": "5cb65a6ec4387ed177e145347e8e308e",
"translation_date": "2025-08-27T00:41:36+00:00",
"source_file": "3-transport/lessons/4-geofences/assignment.md",
"language_code": "ur"
},
"4-manufacturing/README.md": {
"original_hash": "3764e089adf2d5801272bc0895f8498b",
"translation_date": "2025-08-26T21:40:16+00:00",
"source_file": "4-manufacturing/README.md",
"language_code": "ur"
},
"4-manufacturing/lessons/1-train-fruit-detector/README.md": {
"original_hash": "f5e63c916d2dd97d58be12aaf76bd9f1",
"translation_date": "2025-08-26T21:42:42+00:00",
"source_file": "4-manufacturing/lessons/1-train-fruit-detector/README.md",
"language_code": "ur"
},
"4-manufacturing/lessons/1-train-fruit-detector/assignment.md": {
"original_hash": "e74eb2fc7cc3b81916b52e957802f182",
"translation_date": "2025-08-26T21:45:23+00:00",
"source_file": "4-manufacturing/lessons/1-train-fruit-detector/assignment.md",
"language_code": "ur"
},
"4-manufacturing/lessons/2-check-fruit-from-device/README.md": {
"original_hash": "557f4ee96b752e0651d2e6e74aa6bd14",
"translation_date": "2025-08-26T21:46:43+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/README.md",
"language_code": "ur"
},
"4-manufacturing/lessons/2-check-fruit-from-device/assignment.md": {
"original_hash": "022e21f8629b721424c1de25195fff67",
"translation_date": "2025-08-26T21:48:11+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/assignment.md",
"language_code": "ur"
},
"4-manufacturing/lessons/2-check-fruit-from-device/pi-camera.md": {
"original_hash": "c677667095f6133eee418c7e53615d05",
"translation_date": "2025-08-26T21:49:03+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/pi-camera.md",
"language_code": "ur"
},
"4-manufacturing/lessons/2-check-fruit-from-device/single-board-computer-classify-image.md": {
"original_hash": "e5896207b304ce1abaf065b8acc0cc79",
"translation_date": "2025-08-26T21:50:22+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/single-board-computer-classify-image.md",
"language_code": "ur"
},
"4-manufacturing/lessons/2-check-fruit-from-device/virtual-device-camera.md": {
"original_hash": "3ba7150ffc4a6999f6c3cfb4906ec7df",
"translation_date": "2025-08-26T21:53:00+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/virtual-device-camera.md",
"language_code": "ur"
},
"4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-camera.md": {
"original_hash": "160be8c0f558687f6686dca64f10f739",
"translation_date": "2025-08-26T21:55:08+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-camera.md",
"language_code": "ur"
},
"4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-classify-image.md": {
"original_hash": "32a1f23e7834fbe7715da8c4ebb450b9",
"translation_date": "2025-08-26T21:51:34+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-classify-image.md",
"language_code": "ur"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/README.md": {
"original_hash": "2625af24587465c5547ae33d6cc000a5",
"translation_date": "2025-08-26T21:58:25+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/README.md",
"language_code": "ur"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/assignment.md": {
"original_hash": "cc7ad255517f5f618f9c8899e6ff6783",
"translation_date": "2025-08-26T22:01:41+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/assignment.md",
"language_code": "ur"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/single-board-computer.md": {
"original_hash": "50151d9f9dce2801348a93880ef16d86",
"translation_date": "2025-08-26T22:04:09+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/single-board-computer.md",
"language_code": "ur"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/vm-iotedge.md": {
"original_hash": "24dc783a600e20251211987b36370e93",
"translation_date": "2025-08-26T22:02:26+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/vm-iotedge.md",
"language_code": "ur"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/wio-terminal.md": {
"original_hash": "48ac21ec80329c930db7b84bd6b592ec",
"translation_date": "2025-08-26T22:03:30+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/wio-terminal.md",
"language_code": "ur"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/README.md": {
"original_hash": "f74f4ccb61f00e5f7e9f49c3ed416e36",
"translation_date": "2025-08-26T22:06:03+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/README.md",
"language_code": "ur"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/assignment.md": {
"original_hash": "1a85e50c33c38dcd2cde2a97d132f248",
"translation_date": "2025-08-26T22:08:03+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/assignment.md",
"language_code": "ur"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/pi-proximity.md": {
"original_hash": "6145a1d791731c8a9d0afd0a1bae5108",
"translation_date": "2025-08-26T22:08:54+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/pi-proximity.md",
"language_code": "ur"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/virtual-device-proximity.md": {
"original_hash": "7e9f05bdc50a40fd924b1d66934471bf",
"translation_date": "2025-08-26T22:10:58+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/virtual-device-proximity.md",
"language_code": "ur"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/wio-terminal-proximity.md": {
"original_hash": "288aebb0c59f7be1d2719b8f9660a313",
"translation_date": "2025-08-26T22:10:10+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/wio-terminal-proximity.md",
"language_code": "ur"
},
"5-retail/README.md": {
"original_hash": "22a1d6e49f2a689fe5bfa7802a7241fc",
"translation_date": "2025-08-26T21:28:54+00:00",
"source_file": "5-retail/README.md",
"language_code": "ur"
},
"5-retail/lessons/1-train-stock-detector/README.md": {
"original_hash": "8df310a42f902139a01417dacb1ffbef",
"translation_date": "2025-08-26T21:38:05+00:00",
"source_file": "5-retail/lessons/1-train-stock-detector/README.md",
"language_code": "ur"
},
"5-retail/lessons/1-train-stock-detector/assignment.md": {
"original_hash": "d93ee76fac4c2199973689ecd05baaf9",
"translation_date": "2025-08-26T21:39:40+00:00",
"source_file": "5-retail/lessons/1-train-stock-detector/assignment.md",
"language_code": "ur"
},
"5-retail/lessons/2-check-stock-device/README.md": {
"original_hash": "1c9e5fa8b7be726c75a97232b1e41c97",
"translation_date": "2025-08-26T21:31:22+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/README.md",
"language_code": "ur"
},
"5-retail/lessons/2-check-stock-device/assignment.md": {
"original_hash": "3cf7783991ec0ee4f6041223924894c7",
"translation_date": "2025-08-26T21:32:56+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/assignment.md",
"language_code": "ur"
},
"5-retail/lessons/2-check-stock-device/single-board-computer-count-stock.md": {
"original_hash": "9c4320311c0f2c1884a6a21265d98a51",
"translation_date": "2025-08-26T21:35:02+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/single-board-computer-count-stock.md",
"language_code": "ur"
},
"5-retail/lessons/2-check-stock-device/single-board-computer-object-detector.md": {
"original_hash": "a3fdfec1d1e2cb645ea11c2930b51299",
"translation_date": "2025-08-26T21:29:37+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/single-board-computer-object-detector.md",
"language_code": "ur"
},
"5-retail/lessons/2-check-stock-device/wio-terminal-count-stock.md": {
"original_hash": "0b2ae20b0fc8e73c9598dea937cac038",
"translation_date": "2025-08-26T21:33:44+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/wio-terminal-count-stock.md",
"language_code": "ur"
},
"5-retail/lessons/2-check-stock-device/wio-terminal-object-detector.md": {
"original_hash": "4cf1421420a6fab9ab4f2c391bd523b7",
"translation_date": "2025-08-26T21:36:13+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/wio-terminal-object-detector.md",
"language_code": "ur"
},
"6-consumer/README.md": {
"original_hash": "5de7dc1e2ddc402d415473bb795568d4",
"translation_date": "2025-08-26T23:51:18+00:00",
"source_file": "6-consumer/README.md",
"language_code": "ur"
},
"6-consumer/lessons/1-speech-recognition/README.md": {
"original_hash": "6d6aa1be033625d201a190fc9c5cbfb4",
"translation_date": "2025-08-27T00:21:01+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/README.md",
"language_code": "ur"
},
"6-consumer/lessons/1-speech-recognition/assignment.md": {
"original_hash": "5ae7654f519ae831179409dc8e528055",
"translation_date": "2025-08-27T00:23:20+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/assignment.md",
"language_code": "ur"
},
"6-consumer/lessons/1-speech-recognition/pi-audio.md": {
"original_hash": "0ac0afcfb40cb5970ef4cb74f01c32e9",
"translation_date": "2025-08-27T00:18:35+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/pi-audio.md",
"language_code": "ur"
},
"6-consumer/lessons/1-speech-recognition/pi-microphone.md": {
"original_hash": "7e45d884493c5222348b43fbc4481b6a",
"translation_date": "2025-08-27T00:30:30+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/pi-microphone.md",
"language_code": "ur"
},
"6-consumer/lessons/1-speech-recognition/pi-speech-to-text.md": {
"original_hash": "af249a24d4fe4f4de4806adbc3bc9d86",
"translation_date": "2025-08-27T00:32:47+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/pi-speech-to-text.md",
"language_code": "ur"
},
"6-consumer/lessons/1-speech-recognition/virtual-device-audio.md": {
"original_hash": "e4f2925acb211765889c3b51b9116ceb",
"translation_date": "2025-08-27T00:33:42+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/virtual-device-audio.md",
"language_code": "ur"
},
"6-consumer/lessons/1-speech-recognition/virtual-device-microphone.md": {
"original_hash": "7a65ee743f916276a2848b8a9491feb7",
"translation_date": "2025-08-27T00:34:04+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/virtual-device-microphone.md",
"language_code": "ur"
},
"6-consumer/lessons/1-speech-recognition/virtual-device-speech-to-text.md": {
"original_hash": "c0550b254b9ba2539baf1e6bb5fc05f8",
"translation_date": "2025-08-27T00:23:57+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/virtual-device-speech-to-text.md",
"language_code": "ur"
},
"6-consumer/lessons/1-speech-recognition/wio-terminal-audio.md": {
"original_hash": "2f336726b9410e97c3aaed76cc89b0d8",
"translation_date": "2025-08-27T00:25:44+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/wio-terminal-audio.md",
"language_code": "ur"
},
"6-consumer/lessons/1-speech-recognition/wio-terminal-microphone.md": {
"original_hash": "93d352de36526b8990e41dd538100324",
"translation_date": "2025-08-27T00:31:43+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/wio-terminal-microphone.md",
"language_code": "ur"
},
"6-consumer/lessons/1-speech-recognition/wio-terminal-speech-to-text.md": {
"original_hash": "3f92edf2975175577174910caca4a389",
"translation_date": "2025-08-27T00:28:19+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/wio-terminal-speech-to-text.md",
"language_code": "ur"
},
"6-consumer/lessons/2-language-understanding/README.md": {
"original_hash": "6f4ba69d77f16c4a5110623a96a215c3",
"translation_date": "2025-08-27T00:03:49+00:00",
"source_file": "6-consumer/lessons/2-language-understanding/README.md",
"language_code": "ur"
},
"6-consumer/lessons/2-language-understanding/assignment.md": {
"original_hash": "5a7262a0c48dfacdfe1ff91b20bf16fd",
"translation_date": "2025-08-27T00:06:49+00:00",
"source_file": "6-consumer/lessons/2-language-understanding/assignment.md",
"language_code": "ur"
},
"6-consumer/lessons/3-spoken-feedback/README.md": {
"original_hash": "b73fe10ec6b580fba2affb6f6e0a5c4d",
"translation_date": "2025-08-27T00:09:31+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/README.md",
"language_code": "ur"
},
"6-consumer/lessons/3-spoken-feedback/assignment.md": {
"original_hash": "da5d9360fe02fdcc1e91a725016c846d",
"translation_date": "2025-08-27T00:10:43+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/assignment.md",
"language_code": "ur"
},
"6-consumer/lessons/3-spoken-feedback/pi-text-to-speech.md": {
"original_hash": "606f3af1c78e3741e48ce77c31cea626",
"translation_date": "2025-08-27T00:12:57+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/pi-text-to-speech.md",
"language_code": "ur"
},
"6-consumer/lessons/3-spoken-feedback/single-board-computer-set-timer.md": {
"original_hash": "64ad4ddb4de81a18b7252e968f10b404",
"translation_date": "2025-08-27T00:07:35+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/single-board-computer-set-timer.md",
"language_code": "ur"
},
"6-consumer/lessons/3-spoken-feedback/virtual-device-text-to-speech.md": {
"original_hash": "7966848a1f870e4c42edb4db67b13c57",
"translation_date": "2025-08-27T00:17:32+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/virtual-device-text-to-speech.md",
"language_code": "ur"
},
"6-consumer/lessons/3-spoken-feedback/wio-terminal-set-timer.md": {
"original_hash": "012b69d57d898d670adf61304f42a137",
"translation_date": "2025-08-27T00:11:37+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/wio-terminal-set-timer.md",
"language_code": "ur"
},
"6-consumer/lessons/3-spoken-feedback/wio-terminal-text-to-speech.md": {
"original_hash": "a202fa5889790a3777bfc33dd9f4b459",
"translation_date": "2025-08-27T00:15:37+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/wio-terminal-text-to-speech.md",
"language_code": "ur"
},
"6-consumer/lessons/4-multiple-language-support/README.md": {
"original_hash": "c16de27b0074abe81d6a8bad5e5b1a6b",
"translation_date": "2025-08-26T23:54:52+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/README.md",
"language_code": "ur"
},
"6-consumer/lessons/4-multiple-language-support/assignment.md": {
"original_hash": "701f4a4466f9309b6e1d863077df0c06",
"translation_date": "2025-08-26T23:56:28+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/assignment.md",
"language_code": "ur"
},
"6-consumer/lessons/4-multiple-language-support/pi-translate-speech.md": {
"original_hash": "bbb5aa34221fe129dd3ce4d9ec33831a",
"translation_date": "2025-08-26T23:57:16+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/pi-translate-speech.md",
"language_code": "ur"
},
"6-consumer/lessons/4-multiple-language-support/virtual-device-translate-speech.md": {
"original_hash": "d620a470d9dd8614d99824832978360a",
"translation_date": "2025-08-26T23:58:36+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/virtual-device-translate-speech.md",
"language_code": "ur"
},
"6-consumer/lessons/4-multiple-language-support/wio-terminal-translate-speech.md": {
"original_hash": "5f6c164e349f8989959e02a90f37908d",
"translation_date": "2025-08-26T23:52:24+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/wio-terminal-translate-speech.md",
"language_code": "ur"
},
"CODE_OF_CONDUCT.md": {
"original_hash": "c06b12caf3c901eb3156e3dd5b0aea56",
"translation_date": "2025-08-26T21:24:40+00:00",
"source_file": "CODE_OF_CONDUCT.md",
"language_code": "ur"
},
"CONTRIBUTING.md": {
"original_hash": "d6f80293fa9c213283eac7e79b078671",
"translation_date": "2025-08-26T21:28:00+00:00",
"source_file": "CONTRIBUTING.md",
"language_code": "ur"
},
"README.md": {
"original_hash": "3fcc056cd3837e33d2bdf79f9c359fcf",
"translation_date": "2026-01-30T01:15:33+00:00",
"source_file": "README.md",
"language_code": "ur"
},
"SECURITY.md": {
"original_hash": "8587f83cfded1bfab99fda4022f4df89",
"translation_date": "2025-08-26T21:27:22+00:00",
"source_file": "SECURITY.md",
"language_code": "ur"
},
"SUPPORT.md": {
"original_hash": "cd89329575372232e59605f7a08ae0df",
"translation_date": "2025-08-26T21:23:06+00:00",
"source_file": "SUPPORT.md",
"language_code": "ur"
},
"TROUBLESHOOTING.md": {
"original_hash": "9713e21a309662f6fcb271b573d47848",
"translation_date": "2026-01-05T17:32:46+00:00",
"source_file": "TROUBLESHOOTING.md",
"language_code": "ur"
},
"attributions.md": {
"original_hash": "4506d33bbda7acc0ab20980172687090",
"translation_date": "2025-08-26T21:17:09+00:00",
"source_file": "attributions.md",
"language_code": "ur"
},
"clean-up.md": {
"original_hash": "5a94fbab1ba737e9bd6cc6c64f114fa0",
"translation_date": "2025-08-26T21:24:12+00:00",
"source_file": "clean-up.md",
"language_code": "ur"
},
"docs/_sidebar.md": {
"original_hash": "686f22febaa2b67aa03b738c0dc0bf9b",
"translation_date": "2025-08-26T23:08:31+00:00",
"source_file": "docs/_sidebar.md",
"language_code": "ur"
},
"docs/troubleshooting.md": {
"original_hash": "271dcd720357806934b2f0d94e19410e",
"translation_date": "2026-01-05T17:46:04+00:00",
"source_file": "docs/troubleshooting.md",
"language_code": "ur"
},
"for-teachers.md": {
"original_hash": "9fd36f5dc734203ee28b6cf2573e5eab",
"translation_date": "2025-08-26T21:23:35+00:00",
"source_file": "for-teachers.md",
"language_code": "ur"
},
"hardware.md": {
"original_hash": "3dce18fab38adf93ff30b8c221b1eec5",
"translation_date": "2025-08-26T21:25:49+00:00",
"source_file": "hardware.md",
"language_code": "ur"
},
"images/README.md": {
"original_hash": "50abd54997afa7e7a3fc7019379e49e3",
"translation_date": "2025-08-26T21:28:27+00:00",
"source_file": "images/README.md",
"language_code": "ur"
},
"lesson-template/README.md": {
"original_hash": "0494be70ad7fadd13a8c3d549c23e355",
"translation_date": "2025-08-27T01:03:54+00:00",
"source_file": "lesson-template/README.md",
"language_code": "ur"
},
"lesson-template/assignment.md": {
"original_hash": "b5f62ec256c7e43e771f0d3b4e1a9130",
"translation_date": "2025-08-27T01:04:17+00:00",
"source_file": "lesson-template/assignment.md",
"language_code": "ur"
},
"quiz-app/README.md": {
"original_hash": "2a459ea9177fb0508ca96068ae1009d2",
"translation_date": "2025-08-27T01:04:56+00:00",
"source_file": "quiz-app/README.md",
"language_code": "ur"
},
"recommended-learning-model.md": {
"original_hash": "012bbd19f13171be32ac9ba21d4186c2",
"translation_date": "2025-08-26T21:18:09+00:00",
"source_file": "recommended-learning-model.md",
"language_code": "ur"
}
}

@ -0,0 +1,830 @@
{
"1-getting-started/README.md": {
"original_hash": "e2b1b891b08ef7633d285547fbe73290",
"translation_date": "2025-08-24T22:59:18+00:00",
"source_file": "1-getting-started/README.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/1-introduction-to-iot/README.md": {
"original_hash": "9bae08314d8487cb76ddf3d8797e1544",
"translation_date": "2025-08-24T23:26:16+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/README.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/1-introduction-to-iot/assignment.md": {
"original_hash": "7ef1cec2d27b086032d46ab1958f3e99",
"translation_date": "2025-08-24T23:31:31+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/assignment.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/1-introduction-to-iot/pi.md": {
"original_hash": "8ff0d0a1d29832bb896b9c103b69a452",
"translation_date": "2025-08-24T23:34:33+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/pi.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/1-introduction-to-iot/virtual-device.md": {
"original_hash": "52b4de6144b2efdced7797a5339d6035",
"translation_date": "2025-08-24T23:32:01+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/virtual-device.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md": {
"original_hash": "a4f0c166010e31fd7b6ca20bc88dec6d",
"translation_date": "2025-08-24T23:38:10+00:00",
"source_file": "1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/2-deeper-dive/README.md": {
"original_hash": "9dd7f645ad1c6f20b72fee512987f772",
"translation_date": "2025-08-24T23:40:38+00:00",
"source_file": "1-getting-started/lessons/2-deeper-dive/README.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/2-deeper-dive/assignment.md": {
"original_hash": "750bd75866471141f857240219084767",
"translation_date": "2025-08-24T23:47:25+00:00",
"source_file": "1-getting-started/lessons/2-deeper-dive/assignment.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/3-sensors-and-actuators/README.md": {
"original_hash": "e9ee00eb5fc55922a73762acc542166b",
"translation_date": "2025-08-24T23:14:25+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/README.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/3-sensors-and-actuators/assignment.md": {
"original_hash": "c5a568320b1159394108544807895337",
"translation_date": "2025-08-24T23:19:27+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/assignment.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/3-sensors-and-actuators/pi-actuator.md": {
"original_hash": "4db8a3879a53490513571df2f6cf7641",
"translation_date": "2025-08-24T23:20:06+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/pi-actuator.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/3-sensors-and-actuators/pi-sensor.md": {
"original_hash": "ea733bd0cdf2479e082373f765a08678",
"translation_date": "2025-08-24T23:13:20+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/pi-sensor.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/3-sensors-and-actuators/virtual-device-actuator.md": {
"original_hash": "9c640f93263fd9adbfda920739e09feb",
"translation_date": "2025-08-24T23:22:52+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/virtual-device-actuator.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/3-sensors-and-actuators/virtual-device-sensor.md": {
"original_hash": "11f10c6760fb8202cf368422702fdf70",
"translation_date": "2025-08-24T23:24:08+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/virtual-device-sensor.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-actuator.md": {
"original_hash": "db44083b4dc6fb06eac83c4f16448940",
"translation_date": "2025-08-24T23:21:33+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-actuator.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-sensor.md": {
"original_hash": "7f4ad0ef54f248b85b92187c94cf9dcb",
"translation_date": "2025-08-24T23:25:23+00:00",
"source_file": "1-getting-started/lessons/3-sensors-and-actuators/wio-terminal-sensor.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/4-connect-internet/README.md": {
"original_hash": "71b5040e0b3472f1c0949c9b55f224c0",
"translation_date": "2025-08-24T23:01:27+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/README.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/4-connect-internet/assignment.md": {
"original_hash": "0d4033cdd7b5b5475c63770102e38480",
"translation_date": "2025-08-24T23:09:03+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/assignment.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/4-connect-internet/single-board-computer-commands.md": {
"original_hash": "c527ce85d69b1a3875366ec61cbed8aa",
"translation_date": "2025-08-24T22:59:55+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/single-board-computer-commands.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/4-connect-internet/single-board-computer-mqtt.md": {
"original_hash": "90fb93446e03c38f3c0e4009c2471906",
"translation_date": "2025-08-24T23:09:40+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/single-board-computer-mqtt.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/4-connect-internet/single-board-computer-telemetry.md": {
"original_hash": "1226517aae5f5b6f904434670394c688",
"translation_date": "2025-08-24T23:00:44+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/single-board-computer-telemetry.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/4-connect-internet/wio-terminal-commands.md": {
"original_hash": "6754c915dae64ba70fcd5e52c37f3adf",
"translation_date": "2025-08-24T23:12:30+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/wio-terminal-commands.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/4-connect-internet/wio-terminal-mqtt.md": {
"original_hash": "d6faf0e8d3c2d6d20c0aef2a305dab18",
"translation_date": "2025-08-24T23:10:36+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/wio-terminal-mqtt.md",
"language_code": "zh-CN"
},
"1-getting-started/lessons/4-connect-internet/wio-terminal-telemetry.md": {
"original_hash": "4bcc29fe2b65e56eada83d2476279227",
"translation_date": "2025-08-24T23:08:16+00:00",
"source_file": "1-getting-started/lessons/4-connect-internet/wio-terminal-telemetry.md",
"language_code": "zh-CN"
},
"2-farm/README.md": {
"original_hash": "428bda82d9e6016ecea7c797564bf081",
"translation_date": "2025-08-24T21:56:46+00:00",
"source_file": "2-farm/README.md",
"language_code": "zh-CN"
},
"2-farm/lessons/1-predict-plant-growth/README.md": {
"original_hash": "d105b44deae539165855c976dcdeca99",
"translation_date": "2025-08-24T21:57:31+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/README.md",
"language_code": "zh-CN"
},
"2-farm/lessons/1-predict-plant-growth/assignment.md": {
"original_hash": "1e21b012c6685f8bf73e0e76cdca3347",
"translation_date": "2025-08-24T22:02:56+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/assignment.md",
"language_code": "zh-CN"
},
"2-farm/lessons/1-predict-plant-growth/pi-temp.md": {
"original_hash": "7678f7c67b97ee52d5727496dcd7d346",
"translation_date": "2025-08-24T22:07:55+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/pi-temp.md",
"language_code": "zh-CN"
},
"2-farm/lessons/1-predict-plant-growth/single-board-computer-temp-publish.md": {
"original_hash": "4efc74299e19f5d08f2f3f34451a11ba",
"translation_date": "2025-08-24T22:02:03+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/single-board-computer-temp-publish.md",
"language_code": "zh-CN"
},
"2-farm/lessons/1-predict-plant-growth/virtual-device-temp.md": {
"original_hash": "70e5a428b607cd5a9a4f422c2a4df03d",
"translation_date": "2025-08-24T22:06:19+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/virtual-device-temp.md",
"language_code": "zh-CN"
},
"2-farm/lessons/1-predict-plant-growth/wio-terminal-temp-publish.md": {
"original_hash": "df28cd649cd892bcce034e064913b2f3",
"translation_date": "2025-08-24T22:05:20+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/wio-terminal-temp-publish.md",
"language_code": "zh-CN"
},
"2-farm/lessons/1-predict-plant-growth/wio-terminal-temp.md": {
"original_hash": "59263d094f20b302053888cd236880c3",
"translation_date": "2025-08-24T22:03:51+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/wio-terminal-temp.md",
"language_code": "zh-CN"
},
"2-farm/lessons/2-detect-soil-moisture/README.md": {
"original_hash": "4fb20273d299dc8d07a8f06c9cd0cdd9",
"translation_date": "2025-08-24T22:29:08+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/README.md",
"language_code": "zh-CN"
},
"2-farm/lessons/2-detect-soil-moisture/assignment.md": {
"original_hash": "506d21b544d5de47406c89ad496a21cd",
"translation_date": "2025-08-24T22:38:08+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/assignment.md",
"language_code": "zh-CN"
},
"2-farm/lessons/2-detect-soil-moisture/pi-soil-moisture.md": {
"original_hash": "9d4d00a47d5d0f3e6ce42c0d1020064a",
"translation_date": "2025-08-24T22:40:28+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/pi-soil-moisture.md",
"language_code": "zh-CN"
},
"2-farm/lessons/2-detect-soil-moisture/virtual-device-soil-moisture.md": {
"original_hash": "2bf65f162bcebd35fbcba5fd245afac4",
"translation_date": "2025-08-24T22:39:06+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/virtual-device-soil-moisture.md",
"language_code": "zh-CN"
},
"2-farm/lessons/2-detect-soil-moisture/wio-terminal-soil-moisture.md": {
"original_hash": "0d55caa8c23d73635b7559102cd17b8a",
"translation_date": "2025-08-24T22:36:45+00:00",
"source_file": "2-farm/lessons/2-detect-soil-moisture/wio-terminal-soil-moisture.md",
"language_code": "zh-CN"
},
"2-farm/lessons/3-automated-plant-watering/README.md": {
"original_hash": "f7bb24ba53fb627ddb38a8b24a05e594",
"translation_date": "2025-08-24T22:09:20+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/README.md",
"language_code": "zh-CN"
},
"2-farm/lessons/3-automated-plant-watering/assignment.md": {
"original_hash": "ed0fbd6aed084bfba7d5e2f206968c50",
"translation_date": "2025-08-24T22:15:09+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/assignment.md",
"language_code": "zh-CN"
},
"2-farm/lessons/3-automated-plant-watering/pi-relay.md": {
"original_hash": "66b81165e60f8f169bd52a401b6a0f8b",
"translation_date": "2025-08-24T22:17:26+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/pi-relay.md",
"language_code": "zh-CN"
},
"2-farm/lessons/3-automated-plant-watering/virtual-device-relay.md": {
"original_hash": "f8f541ee945545017a51aaf309aa37c3",
"translation_date": "2025-08-24T22:18:50+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/virtual-device-relay.md",
"language_code": "zh-CN"
},
"2-farm/lessons/3-automated-plant-watering/wio-terminal-relay.md": {
"original_hash": "f3c5d8afa2ef6a0b425ef8ff20615cb4",
"translation_date": "2025-08-24T22:16:17+00:00",
"source_file": "2-farm/lessons/3-automated-plant-watering/wio-terminal-relay.md",
"language_code": "zh-CN"
},
"2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md": {
"original_hash": "4d8e7a066d75b625e7a979c14157041d",
"translation_date": "2025-08-24T22:41:43+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md",
"language_code": "zh-CN"
},
"2-farm/lessons/4-migrate-your-plant-to-the-cloud/assignment.md": {
"original_hash": "bfd35499bd68d7d740242bfea784bbeb",
"translation_date": "2025-08-24T22:47:53+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/assignment.md",
"language_code": "zh-CN"
},
"2-farm/lessons/4-migrate-your-plant-to-the-cloud/single-board-computer-connect-hub.md": {
"original_hash": "3ac42e284a7222c0e83d2d43231a364f",
"translation_date": "2025-08-24T22:48:29+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/single-board-computer-connect-hub.md",
"language_code": "zh-CN"
},
"2-farm/lessons/4-migrate-your-plant-to-the-cloud/wio-terminal-connect-hub.md": {
"original_hash": "28320305a35ea3bc59c41fe146a2e6ed",
"translation_date": "2025-08-24T22:49:39+00:00",
"source_file": "2-farm/lessons/4-migrate-your-plant-to-the-cloud/wio-terminal-connect-hub.md",
"language_code": "zh-CN"
},
"2-farm/lessons/5-migrate-application-to-the-cloud/README.md": {
"original_hash": "5f2d2f4a5a023c93ab34a0cc5b47c0c4",
"translation_date": "2025-08-24T22:20:07+00:00",
"source_file": "2-farm/lessons/5-migrate-application-to-the-cloud/README.md",
"language_code": "zh-CN"
},
"2-farm/lessons/5-migrate-application-to-the-cloud/assignment.md": {
"original_hash": "c24b6e4d90501c9199f2ceb6a648a337",
"translation_date": "2025-08-24T22:27:32+00:00",
"source_file": "2-farm/lessons/5-migrate-application-to-the-cloud/assignment.md",
"language_code": "zh-CN"
},
"2-farm/lessons/6-keep-your-plant-secure/README.md": {
"original_hash": "81c437c568eee1b0dda1f04e88150d37",
"translation_date": "2025-08-24T22:51:35+00:00",
"source_file": "2-farm/lessons/6-keep-your-plant-secure/README.md",
"language_code": "zh-CN"
},
"2-farm/lessons/6-keep-your-plant-secure/assignment.md": {
"original_hash": "34010c663d96d5f419eda6ac2366a78d",
"translation_date": "2025-08-24T22:56:29+00:00",
"source_file": "2-farm/lessons/6-keep-your-plant-secure/assignment.md",
"language_code": "zh-CN"
},
"2-farm/lessons/6-keep-your-plant-secure/single-board-computer-x509.md": {
"original_hash": "9aea84bcc7520222b0e1c50469d62d6a",
"translation_date": "2025-08-24T22:57:09+00:00",
"source_file": "2-farm/lessons/6-keep-your-plant-secure/single-board-computer-x509.md",
"language_code": "zh-CN"
},
"2-farm/lessons/6-keep-your-plant-secure/wio-terminal-x509.md": {
"original_hash": "8a74f789f3c1bf41a13c007190360c19",
"translation_date": "2025-08-24T22:58:05+00:00",
"source_file": "2-farm/lessons/6-keep-your-plant-secure/wio-terminal-x509.md",
"language_code": "zh-CN"
},
"3-transport/README.md": {
"original_hash": "e978534a245b000725ed2a048f943213",
"translation_date": "2025-08-25T00:35:23+00:00",
"source_file": "3-transport/README.md",
"language_code": "zh-CN"
},
"3-transport/lessons/1-location-tracking/README.md": {
"original_hash": "52ed2bd997d08040f79a1a6ef2bac958",
"translation_date": "2025-08-25T00:47:03+00:00",
"source_file": "3-transport/lessons/1-location-tracking/README.md",
"language_code": "zh-CN"
},
"3-transport/lessons/1-location-tracking/assignment.md": {
"original_hash": "bded364fc06ce37d7a76aed3be1ba73a",
"translation_date": "2025-08-25T00:51:25+00:00",
"source_file": "3-transport/lessons/1-location-tracking/assignment.md",
"language_code": "zh-CN"
},
"3-transport/lessons/1-location-tracking/pi-gps-sensor.md": {
"original_hash": "3b2448c7ab4e9673e77e35a50c5e350d",
"translation_date": "2025-08-25T00:52:46+00:00",
"source_file": "3-transport/lessons/1-location-tracking/pi-gps-sensor.md",
"language_code": "zh-CN"
},
"3-transport/lessons/1-location-tracking/single-board-computer-gps-decode.md": {
"original_hash": "cbb8c285bc64c5192fae3368fb5077d2",
"translation_date": "2025-08-25T00:52:02+00:00",
"source_file": "3-transport/lessons/1-location-tracking/single-board-computer-gps-decode.md",
"language_code": "zh-CN"
},
"3-transport/lessons/1-location-tracking/virtual-device-gps-sensor.md": {
"original_hash": "64f18a8f8aaa1fef5e7320e0992d8b3a",
"translation_date": "2025-08-25T00:45:25+00:00",
"source_file": "3-transport/lessons/1-location-tracking/virtual-device-gps-sensor.md",
"language_code": "zh-CN"
},
"3-transport/lessons/1-location-tracking/wio-terminal-gps-decode.md": {
"original_hash": "fbbcf96a9b63ccd661db98bbf854bb06",
"translation_date": "2025-08-25T00:54:32+00:00",
"source_file": "3-transport/lessons/1-location-tracking/wio-terminal-gps-decode.md",
"language_code": "zh-CN"
},
"3-transport/lessons/1-location-tracking/wio-terminal-gps-sensor.md": {
"original_hash": "da6ae0a795cf06be33d23ca5b8493fc8",
"translation_date": "2025-08-25T00:44:02+00:00",
"source_file": "3-transport/lessons/1-location-tracking/wio-terminal-gps-sensor.md",
"language_code": "zh-CN"
},
"3-transport/lessons/2-store-location-data/README.md": {
"original_hash": "e345843ccfeb7261d81500d19c64d476",
"translation_date": "2025-08-25T01:00:18+00:00",
"source_file": "3-transport/lessons/2-store-location-data/README.md",
"language_code": "zh-CN"
},
"3-transport/lessons/2-store-location-data/assignment.md": {
"original_hash": "b2e0a965723082b068f735aec0faf3f6",
"translation_date": "2025-08-25T01:07:12+00:00",
"source_file": "3-transport/lessons/2-store-location-data/assignment.md",
"language_code": "zh-CN"
},
"3-transport/lessons/3-visualize-location-data/README.md": {
"original_hash": "9095c61445c2bca7245ef9b59a186a11",
"translation_date": "2025-08-25T00:55:20+00:00",
"source_file": "3-transport/lessons/3-visualize-location-data/README.md",
"language_code": "zh-CN"
},
"3-transport/lessons/3-visualize-location-data/assignment.md": {
"original_hash": "0ccdc1faa676a485c4c6ecbddb9f9067",
"translation_date": "2025-08-25T00:59:40+00:00",
"source_file": "3-transport/lessons/3-visualize-location-data/assignment.md",
"language_code": "zh-CN"
},
"3-transport/lessons/4-geofences/README.md": {
"original_hash": "078ae664c7b686bf069545e9a5fc95b2",
"translation_date": "2025-08-25T00:36:23+00:00",
"source_file": "3-transport/lessons/4-geofences/README.md",
"language_code": "zh-CN"
},
"3-transport/lessons/4-geofences/assignment.md": {
"original_hash": "5cb65a6ec4387ed177e145347e8e308e",
"translation_date": "2025-08-25T00:43:18+00:00",
"source_file": "3-transport/lessons/4-geofences/assignment.md",
"language_code": "zh-CN"
},
"4-manufacturing/README.md": {
"original_hash": "3764e089adf2d5801272bc0895f8498b",
"translation_date": "2025-08-24T21:19:05+00:00",
"source_file": "4-manufacturing/README.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/1-train-fruit-detector/README.md": {
"original_hash": "f5e63c916d2dd97d58be12aaf76bd9f1",
"translation_date": "2025-08-24T21:20:11+00:00",
"source_file": "4-manufacturing/lessons/1-train-fruit-detector/README.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/1-train-fruit-detector/assignment.md": {
"original_hash": "e74eb2fc7cc3b81916b52e957802f182",
"translation_date": "2025-08-24T21:25:00+00:00",
"source_file": "4-manufacturing/lessons/1-train-fruit-detector/assignment.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/2-check-fruit-from-device/README.md": {
"original_hash": "557f4ee96b752e0651d2e6e74aa6bd14",
"translation_date": "2025-08-24T21:25:38+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/README.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/2-check-fruit-from-device/assignment.md": {
"original_hash": "022e21f8629b721424c1de25195fff67",
"translation_date": "2025-08-24T21:28:29+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/assignment.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/2-check-fruit-from-device/pi-camera.md": {
"original_hash": "c677667095f6133eee418c7e53615d05",
"translation_date": "2025-08-24T21:29:02+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/pi-camera.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/2-check-fruit-from-device/single-board-computer-classify-image.md": {
"original_hash": "e5896207b304ce1abaf065b8acc0cc79",
"translation_date": "2025-08-24T21:30:55+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/single-board-computer-classify-image.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/2-check-fruit-from-device/virtual-device-camera.md": {
"original_hash": "3ba7150ffc4a6999f6c3cfb4906ec7df",
"translation_date": "2025-08-24T21:33:33+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/virtual-device-camera.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-camera.md": {
"original_hash": "160be8c0f558687f6686dca64f10f739",
"translation_date": "2025-08-24T21:34:57+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-camera.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-classify-image.md": {
"original_hash": "32a1f23e7834fbe7715da8c4ebb450b9",
"translation_date": "2025-08-24T21:32:02+00:00",
"source_file": "4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-classify-image.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/README.md": {
"original_hash": "2625af24587465c5547ae33d6cc000a5",
"translation_date": "2025-08-24T21:37:51+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/README.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/assignment.md": {
"original_hash": "cc7ad255517f5f618f9c8899e6ff6783",
"translation_date": "2025-08-24T21:44:47+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/assignment.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/single-board-computer.md": {
"original_hash": "50151d9f9dce2801348a93880ef16d86",
"translation_date": "2025-08-24T21:47:29+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/single-board-computer.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/vm-iotedge.md": {
"original_hash": "24dc783a600e20251211987b36370e93",
"translation_date": "2025-08-24T21:45:27+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/vm-iotedge.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/3-run-fruit-detector-edge/wio-terminal.md": {
"original_hash": "48ac21ec80329c930db7b84bd6b592ec",
"translation_date": "2025-08-24T21:46:40+00:00",
"source_file": "4-manufacturing/lessons/3-run-fruit-detector-edge/wio-terminal.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/README.md": {
"original_hash": "f74f4ccb61f00e5f7e9f49c3ed416e36",
"translation_date": "2025-08-24T21:48:17+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/README.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/assignment.md": {
"original_hash": "1a85e50c33c38dcd2cde2a97d132f248",
"translation_date": "2025-08-24T21:52:39+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/assignment.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/pi-proximity.md": {
"original_hash": "6145a1d791731c8a9d0afd0a1bae5108",
"translation_date": "2025-08-24T21:53:26+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/pi-proximity.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/virtual-device-proximity.md": {
"original_hash": "7e9f05bdc50a40fd924b1d66934471bf",
"translation_date": "2025-08-24T21:55:38+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/virtual-device-proximity.md",
"language_code": "zh-CN"
},
"4-manufacturing/lessons/4-trigger-fruit-detector/wio-terminal-proximity.md": {
"original_hash": "288aebb0c59f7be1d2719b8f9660a313",
"translation_date": "2025-08-24T21:54:32+00:00",
"source_file": "4-manufacturing/lessons/4-trigger-fruit-detector/wio-terminal-proximity.md",
"language_code": "zh-CN"
},
"5-retail/README.md": {
"original_hash": "22a1d6e49f2a689fe5bfa7802a7241fc",
"translation_date": "2025-08-24T21:05:47+00:00",
"source_file": "5-retail/README.md",
"language_code": "zh-CN"
},
"5-retail/lessons/1-train-stock-detector/README.md": {
"original_hash": "8df310a42f902139a01417dacb1ffbef",
"translation_date": "2025-08-24T21:14:49+00:00",
"source_file": "5-retail/lessons/1-train-stock-detector/README.md",
"language_code": "zh-CN"
},
"5-retail/lessons/1-train-stock-detector/assignment.md": {
"original_hash": "d93ee76fac4c2199973689ecd05baaf9",
"translation_date": "2025-08-24T21:18:15+00:00",
"source_file": "5-retail/lessons/1-train-stock-detector/assignment.md",
"language_code": "zh-CN"
},
"5-retail/lessons/2-check-stock-device/README.md": {
"original_hash": "1c9e5fa8b7be726c75a97232b1e41c97",
"translation_date": "2025-08-24T21:07:33+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/README.md",
"language_code": "zh-CN"
},
"5-retail/lessons/2-check-stock-device/assignment.md": {
"original_hash": "3cf7783991ec0ee4f6041223924894c7",
"translation_date": "2025-08-24T21:10:27+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/assignment.md",
"language_code": "zh-CN"
},
"5-retail/lessons/2-check-stock-device/single-board-computer-count-stock.md": {
"original_hash": "9c4320311c0f2c1884a6a21265d98a51",
"translation_date": "2025-08-24T21:12:13+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/single-board-computer-count-stock.md",
"language_code": "zh-CN"
},
"5-retail/lessons/2-check-stock-device/single-board-computer-object-detector.md": {
"original_hash": "a3fdfec1d1e2cb645ea11c2930b51299",
"translation_date": "2025-08-24T21:06:27+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/single-board-computer-object-detector.md",
"language_code": "zh-CN"
},
"5-retail/lessons/2-check-stock-device/wio-terminal-count-stock.md": {
"original_hash": "0b2ae20b0fc8e73c9598dea937cac038",
"translation_date": "2025-08-24T21:11:03+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/wio-terminal-count-stock.md",
"language_code": "zh-CN"
},
"5-retail/lessons/2-check-stock-device/wio-terminal-object-detector.md": {
"original_hash": "4cf1421420a6fab9ab4f2c391bd523b7",
"translation_date": "2025-08-24T21:13:43+00:00",
"source_file": "5-retail/lessons/2-check-stock-device/wio-terminal-object-detector.md",
"language_code": "zh-CN"
},
"6-consumer/README.md": {
"original_hash": "5de7dc1e2ddc402d415473bb795568d4",
"translation_date": "2025-08-24T23:48:01+00:00",
"source_file": "6-consumer/README.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/1-speech-recognition/README.md": {
"original_hash": "6d6aa1be033625d201a190fc9c5cbfb4",
"translation_date": "2025-08-25T00:18:26+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/README.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/1-speech-recognition/assignment.md": {
"original_hash": "5ae7654f519ae831179409dc8e528055",
"translation_date": "2025-08-25T00:22:53+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/assignment.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/1-speech-recognition/pi-audio.md": {
"original_hash": "0ac0afcfb40cb5970ef4cb74f01c32e9",
"translation_date": "2025-08-25T00:16:38+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/pi-audio.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/1-speech-recognition/pi-microphone.md": {
"original_hash": "7e45d884493c5222348b43fbc4481b6a",
"translation_date": "2025-08-25T00:30:10+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/pi-microphone.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/1-speech-recognition/pi-speech-to-text.md": {
"original_hash": "af249a24d4fe4f4de4806adbc3bc9d86",
"translation_date": "2025-08-25T00:33:08+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/pi-speech-to-text.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/1-speech-recognition/virtual-device-audio.md": {
"original_hash": "e4f2925acb211765889c3b51b9116ceb",
"translation_date": "2025-08-25T00:34:25+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/virtual-device-audio.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/1-speech-recognition/virtual-device-microphone.md": {
"original_hash": "7a65ee743f916276a2848b8a9491feb7",
"translation_date": "2025-08-25T00:34:54+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/virtual-device-microphone.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/1-speech-recognition/virtual-device-speech-to-text.md": {
"original_hash": "c0550b254b9ba2539baf1e6bb5fc05f8",
"translation_date": "2025-08-25T00:23:19+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/virtual-device-speech-to-text.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/1-speech-recognition/wio-terminal-audio.md": {
"original_hash": "2f336726b9410e97c3aaed76cc89b0d8",
"translation_date": "2025-08-25T00:24:20+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/wio-terminal-audio.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/1-speech-recognition/wio-terminal-microphone.md": {
"original_hash": "93d352de36526b8990e41dd538100324",
"translation_date": "2025-08-25T00:31:42+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/wio-terminal-microphone.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/1-speech-recognition/wio-terminal-speech-to-text.md": {
"original_hash": "3f92edf2975175577174910caca4a389",
"translation_date": "2025-08-25T00:27:32+00:00",
"source_file": "6-consumer/lessons/1-speech-recognition/wio-terminal-speech-to-text.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/2-language-understanding/README.md": {
"original_hash": "6f4ba69d77f16c4a5110623a96a215c3",
"translation_date": "2025-08-24T23:57:28+00:00",
"source_file": "6-consumer/lessons/2-language-understanding/README.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/2-language-understanding/assignment.md": {
"original_hash": "5a7262a0c48dfacdfe1ff91b20bf16fd",
"translation_date": "2025-08-25T00:03:47+00:00",
"source_file": "6-consumer/lessons/2-language-understanding/assignment.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/3-spoken-feedback/README.md": {
"original_hash": "b73fe10ec6b580fba2affb6f6e0a5c4d",
"translation_date": "2025-08-25T00:05:39+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/README.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/3-spoken-feedback/assignment.md": {
"original_hash": "da5d9360fe02fdcc1e91a725016c846d",
"translation_date": "2025-08-25T00:07:59+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/assignment.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/3-spoken-feedback/pi-text-to-speech.md": {
"original_hash": "606f3af1c78e3741e48ce77c31cea626",
"translation_date": "2025-08-25T00:10:24+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/pi-text-to-speech.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/3-spoken-feedback/single-board-computer-set-timer.md": {
"original_hash": "64ad4ddb4de81a18b7252e968f10b404",
"translation_date": "2025-08-25T00:04:25+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/single-board-computer-set-timer.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/3-spoken-feedback/virtual-device-text-to-speech.md": {
"original_hash": "7966848a1f870e4c42edb4db67b13c57",
"translation_date": "2025-08-25T00:15:39+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/virtual-device-text-to-speech.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/3-spoken-feedback/wio-terminal-set-timer.md": {
"original_hash": "012b69d57d898d670adf61304f42a137",
"translation_date": "2025-08-25T00:08:41+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/wio-terminal-set-timer.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/3-spoken-feedback/wio-terminal-text-to-speech.md": {
"original_hash": "a202fa5889790a3777bfc33dd9f4b459",
"translation_date": "2025-08-25T00:11:49+00:00",
"source_file": "6-consumer/lessons/3-spoken-feedback/wio-terminal-text-to-speech.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/4-multiple-language-support/README.md": {
"original_hash": "c16de27b0074abe81d6a8bad5e5b1a6b",
"translation_date": "2025-08-24T23:50:42+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/README.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/4-multiple-language-support/assignment.md": {
"original_hash": "701f4a4466f9309b6e1d863077df0c06",
"translation_date": "2025-08-24T23:53:46+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/assignment.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/4-multiple-language-support/pi-translate-speech.md": {
"original_hash": "bbb5aa34221fe129dd3ce4d9ec33831a",
"translation_date": "2025-08-24T23:54:28+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/pi-translate-speech.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/4-multiple-language-support/virtual-device-translate-speech.md": {
"original_hash": "d620a470d9dd8614d99824832978360a",
"translation_date": "2025-08-24T23:55:44+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/virtual-device-translate-speech.md",
"language_code": "zh-CN"
},
"6-consumer/lessons/4-multiple-language-support/wio-terminal-translate-speech.md": {
"original_hash": "5f6c164e349f8989959e02a90f37908d",
"translation_date": "2025-08-24T23:48:47+00:00",
"source_file": "6-consumer/lessons/4-multiple-language-support/wio-terminal-translate-speech.md",
"language_code": "zh-CN"
},
"CODE_OF_CONDUCT.md": {
"original_hash": "c06b12caf3c901eb3156e3dd5b0aea56",
"translation_date": "2025-08-24T21:01:10+00:00",
"source_file": "CODE_OF_CONDUCT.md",
"language_code": "zh-CN"
},
"CONTRIBUTING.md": {
"original_hash": "d6f80293fa9c213283eac7e79b078671",
"translation_date": "2025-08-24T21:04:42+00:00",
"source_file": "CONTRIBUTING.md",
"language_code": "zh-CN"
},
"README.md": {
"original_hash": "3fcc056cd3837e33d2bdf79f9c359fcf",
"translation_date": "2026-01-30T01:16:53+00:00",
"source_file": "README.md",
"language_code": "zh-CN"
},
"SECURITY.md": {
"original_hash": "8587f83cfded1bfab99fda4022f4df89",
"translation_date": "2025-08-24T21:03:51+00:00",
"source_file": "SECURITY.md",
"language_code": "zh-CN"
},
"SUPPORT.md": {
"original_hash": "cd89329575372232e59605f7a08ae0df",
"translation_date": "2025-08-24T20:59:13+00:00",
"source_file": "SUPPORT.md",
"language_code": "zh-CN"
},
"TROUBLESHOOTING.md": {
"original_hash": "9713e21a309662f6fcb271b573d47848",
"translation_date": "2026-01-05T17:35:58+00:00",
"source_file": "TROUBLESHOOTING.md",
"language_code": "zh-CN"
},
"attributions.md": {
"original_hash": "4506d33bbda7acc0ab20980172687090",
"translation_date": "2025-08-24T20:52:03+00:00",
"source_file": "attributions.md",
"language_code": "zh-CN"
},
"clean-up.md": {
"original_hash": "5a94fbab1ba737e9bd6cc6c64f114fa0",
"translation_date": "2025-08-24T21:00:27+00:00",
"source_file": "clean-up.md",
"language_code": "zh-CN"
},
"docs/_sidebar.md": {
"original_hash": "686f22febaa2b67aa03b738c0dc0bf9b",
"translation_date": "2025-08-24T22:58:32+00:00",
"source_file": "docs/_sidebar.md",
"language_code": "zh-CN"
},
"docs/troubleshooting.md": {
"original_hash": "271dcd720357806934b2f0d94e19410e",
"translation_date": "2026-01-05T17:46:15+00:00",
"source_file": "docs/troubleshooting.md",
"language_code": "zh-CN"
},
"for-teachers.md": {
"original_hash": "9fd36f5dc734203ee28b6cf2573e5eab",
"translation_date": "2025-08-24T20:59:40+00:00",
"source_file": "for-teachers.md",
"language_code": "zh-CN"
},
"hardware.md": {
"original_hash": "3dce18fab38adf93ff30b8c221b1eec5",
"translation_date": "2025-08-24T21:01:35+00:00",
"source_file": "hardware.md",
"language_code": "zh-CN"
},
"images/README.md": {
"original_hash": "50abd54997afa7e7a3fc7019379e49e3",
"translation_date": "2025-08-24T21:05:15+00:00",
"source_file": "images/README.md",
"language_code": "zh-CN"
},
"lesson-template/README.md": {
"original_hash": "0494be70ad7fadd13a8c3d549c23e355",
"translation_date": "2025-08-25T01:07:54+00:00",
"source_file": "lesson-template/README.md",
"language_code": "zh-CN"
},
"lesson-template/assignment.md": {
"original_hash": "b5f62ec256c7e43e771f0d3b4e1a9130",
"translation_date": "2025-08-25T01:08:30+00:00",
"source_file": "lesson-template/assignment.md",
"language_code": "zh-CN"
},
"quiz-app/README.md": {
"original_hash": "2a459ea9177fb0508ca96068ae1009d2",
"translation_date": "2025-08-25T01:08:55+00:00",
"source_file": "quiz-app/README.md",
"language_code": "zh-CN"
},
"recommended-learning-model.md": {
"original_hash": "012bbd19f13171be32ac9ba21d4186c2",
"translation_date": "2025-08-24T20:53:19+00:00",
"source_file": "recommended-learning-model.md",
"language_code": "zh-CN"
}
}

@ -0,0 +1,19 @@
# 开始学习物联网
在本课程的这一部分中您将了解物联网IoT学习基本概念包括构建您的第一个“Hello World”物联网项目并连接到云端。这个项目是一个夜灯当传感器测量到光线水平下降时夜灯会亮起。
![LED连接到WIO随着光线水平变化而亮起和熄灭](../../../images/wio-running-assignment-1-1.gif)
## 主题
1. [物联网简介](lessons/1-introduction-to-iot/README.md)
1. [深入了解物联网](lessons/2-deeper-dive/README.md)
1. [使用传感器和执行器与物理世界交互](lessons/3-sensors-and-actuators/README.md)
1. [将您的设备连接到互联网](lessons/4-connect-internet/README.md)
## 致谢
所有课程由 [Jim Bennett](https://GitHub.com/JimBobBennett) 倾注 ♥️ 编写
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,231 @@
# 物联网简介
![本课程的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-1.2606670fa61ee904687da5d6fa4e726639d524d064c895117da1b95b9ff6251d.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看大图。
本课程是 [Microsoft Reactor](https://developer.microsoft.com/reactor/?WT.mc_id=academic-17441-jabenn) 的 [Hello IoT 系列](https://youtube.com/playlist?list=PLmsFUfdnGr3xRts0TIwyaHyQuHaNQcb6-) 的一部分。课程分为两部分:一个小时的课程和一个小时的答疑环节,深入探讨课程内容并回答问题。
[![课程 1物联网简介](https://img.youtube.com/vi/bVFfcYh6UBw/0.jpg)](https://youtu.be/bVFfcYh6UBw)
[![课程 1物联网简介 - 答疑环节](https://img.youtube.com/vi/YI772q5v3yI/0.jpg)](https://youtu.be/YI772q5v3yI)
> 🎥 点击上方图片观看视频
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/1)
## 简介
本课程涵盖了物联网的一些基础知识,并帮助你开始设置硬件。
在本课程中,我们将讨论以下内容:
* [什么是“物联网”?](../../../../../1-getting-started/lessons/1-introduction-to-iot)
* [物联网设备](../../../../../1-getting-started/lessons/1-introduction-to-iot)
* [设置你的设备](../../../../../1-getting-started/lessons/1-introduction-to-iot)
* [物联网的应用](../../../../../1-getting-started/lessons/1-introduction-to-iot)
* [你身边可能存在的物联网设备示例](../../../../../1-getting-started/lessons/1-introduction-to-iot)
## 什么是“物联网”?
“物联网”这一术语由 [Kevin Ashton](https://wikipedia.org/wiki/Kevin_Ashton) 于 1999 年提出,用来描述通过传感器将互联网与物理世界连接起来。从那时起,这个术语被用来描述任何与周围物理世界交互的设备,无论是通过传感器收集数据,还是通过执行器(例如打开开关或点亮 LED 的设备)提供现实世界的交互,这些设备通常连接到其他设备或互联网。
> **传感器** 从世界中收集信息,例如测量速度、温度或位置。
>
> **执行器** 将电信号转换为现实世界的交互,例如触发开关、点亮灯光、发出声音或向其他硬件发送控制信号,例如打开电源插座。
物联网不仅仅是设备,它还包括基于云的服务,这些服务可以处理传感器数据或向连接到物联网设备的执行器发送请求。它还包括不具备或不需要互联网连接的设备,通常称为边缘设备。这些设备可以自行处理和响应传感器数据,通常使用在云中训练的 AI 模型。
物联网是一个快速发展的技术领域。据估计,到 2020 年底,已有 300 亿物联网设备被部署并连接到互联网。展望未来,据估计到 2025 年,物联网设备将收集近 80 ZBzettabytes即 80 万亿 GB的数据。这是一个庞大的数据量
![一张显示活跃物联网设备数量随时间增长的图表,从 2015 年的不到 50 亿增长到 2025 年的超过 300 亿](../../../../../images/connected-iot-devices.svg)
✅ 做一些研究:物联网设备生成的数据中有多少被实际使用了?有多少被浪费了?为什么会有这么多数据被忽略?
这些数据是物联网成功的关键。要成为一名成功的物联网开发者,你需要了解需要收集的数据、如何收集数据、如何基于数据做出决策,以及在需要时如何利用这些决策与物理世界交互。
## 物联网设备
物联网中的 **T** 代表 **Things**(设备)——与周围物理世界交互的设备,无论是通过传感器收集数据,还是通过执行器提供现实世界的交互。
用于生产或商业用途的设备,例如消费者健身追踪器或工业机器控制器,通常是定制制造的。这些设备使用定制电路板,甚至可能使用定制处理器,以满足特定任务的需求,无论是小到可以戴在手腕上,还是坚固到可以在高温、高压力或高振动的工厂环境中工作。
作为一名学习物联网或创建设备原型的开发者,你需要从开发套件开始。这些是为开发者设计的通用物联网设备,通常具有生产设备所没有的功能,例如一组外部引脚,用于连接传感器或执行器,支持调试的硬件,或在大规模制造时会增加不必要成本的额外资源。
这些开发套件通常分为两类——微控制器和单板计算机。这里将介绍这两类设备,下一课中会详细讲解。
> 💁 你的手机也可以被认为是一种通用物联网设备,内置了传感器和执行器,不同的应用程序以不同的方式使用这些传感器和执行器,并与不同的云服务交互。你甚至可以找到一些使用手机应用程序作为物联网设备的教程。
### 微控制器
微控制器(也称为 MCUMicrocontroller Unit 的缩写)是一种小型计算机,由以下部分组成:
🧠 一个或多个中央处理单元CPU——微控制器的“大脑”运行你的程序
💾 内存RAM 和程序内存)——存储你的程序、数据和变量
🔌 可编程输入/输出I/O连接——与外部设备连接的传感器和执行器通信
微控制器通常是低成本的计算设备,用于定制硬件的微控制器平均价格约为 0.50 美元,有些设备甚至低至 0.03 美元。开发套件的起价约为 4 美元,随着功能的增加成本也会增加。[Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html) 是 [Seeed Studios](https://www.seeedstudio.com) 的一个微控制器开发套件配备传感器、执行器、WiFi 和屏幕,价格约为 30 美元。
![Wio Terminal](../../../../../translated_images/zh-CN/wio-terminal.b8299ee16587db9a.webp)
> 💁 在网上搜索微控制器时,请注意搜索术语 **MCU**,因为这可能会返回大量关于“漫威电影宇宙”的结果,而不是微控制器。
微控制器设计用于执行有限数量的非常具体的任务,而不是像 PC 或 Mac 那样的通用计算机。除非是非常特殊的场景,你无法连接显示器、键盘和鼠标来执行通用任务。
微控制器开发套件通常配备额外的传感器和执行器。大多数开发板会有一个或多个可编程的 LED以及其他设备例如标准插头用于通过各种制造商的生态系统添加更多传感器或执行器或者内置传感器通常是最受欢迎的传感器例如温度传感器。一些微控制器内置无线连接功能例如蓝牙或 WiFi或者在开发板上添加额外的微控制器以提供这些连接功能。
> 💁 微控制器通常使用 C/C++ 编程。
### 单板计算机
单板计算机是一种小型计算设备,所有计算机的组成部分都集成在一个小型电路板上。这些设备的规格接近台式机或笔记本电脑 PC 或 Mac运行完整的操作系统但体积更小功耗更低价格也更便宜。
![Raspberry Pi 4](../../../../../translated_images/zh-CN/raspberry-pi-4.fd4590d308c3d456.webp)
Raspberry Pi 是最受欢迎的单板计算机之一。
与微控制器类似,单板计算机具有 CPU、内存和输入/输出引脚,但它们还具有额外的功能,例如图形芯片,可以连接显示器、音频输出和 USB 端口,用于连接键盘、鼠标和其他标准 USB 设备(如摄像头或外部存储)。程序和操作系统存储在 SD 卡或硬盘上,而不是内置在开发板上的内存芯片中。
> 🎓 你可以将单板计算机看作是你正在使用的 PC 或 Mac 的一个更小、更便宜的版本,并额外配备了 GPIO通用输入/输出)引脚,用于与传感器和执行器交互。
单板计算机是功能齐全的计算机,因此可以使用任何编程语言进行编程。物联网设备通常使用 Python 编程。
### 后续课程的硬件选择
后续课程包括使用物联网设备与物理世界交互并与云通信的作业。每节课支持三种设备选择——Arduino使用 Seeed Studios 的 Wio Terminal或单板计算机物理设备 Raspberry Pi 4 或运行在 PC 或 Mac 上的虚拟单板计算机)。
你可以在 [硬件指南](../../../hardware.md) 中阅读完成所有作业所需的硬件。
> 💁 你不需要购买任何物联网硬件即可完成作业,你可以使用虚拟单板计算机完成所有任务。
选择哪种硬件取决于你家里或学校的可用设备,以及你熟悉或计划学习的编程语言。两种硬件选项将使用相同的传感器生态系统,因此如果你选择了一种路径,之后可以切换到另一种路径,而无需更换大部分设备。虚拟单板计算机相当于学习 Raspberry Pi代码大部分可以转移到实际的 Pi 上,如果你最终购买了设备和传感器。
### Arduino 开发套件
如果你对学习微控制器开发感兴趣,可以使用 Arduino 设备完成作业。你需要对 C/C++ 编程有基本了解,因为课程只会教授与 Arduino 框架、传感器和执行器以及与云交互的库相关的代码。
作业将使用 [Visual Studio Code](https://code.visualstudio.com/?WT.mc_id=academic-17441-jabenn) 和 [PlatformIO 微控制器开发扩展](https://platformio.org)。如果你熟悉 Arduino IDE也可以使用该工具但课程不会提供相关说明。
### 单板计算机开发套件
如果你对使用单板计算机进行物联网开发感兴趣,可以使用 Raspberry Pi 或运行在 PC 或 Mac 上的虚拟设备完成作业。
你需要对 Python 编程有基本了解,因为课程只会教授与传感器和执行器以及与云交互的库相关的代码。
> 💁 如果你想学习 Python 编程,可以查看以下两个视频系列:
>
> * [Python 初学者教程](https://channel9.msdn.com/Series/Intro-to-Python-Development?WT.mc_id=academic-17441-jabenn)
> * [Python 进阶教程](https://channel9.msdn.com/Series/More-Python-for-Beginners?WT.mc_id=academic-7372-jabenn)
作业将使用 [Visual Studio Code](https://code.visualstudio.com/?WT.mc_id=academic-17441-jabenn)。
如果你使用 Raspberry Pi可以选择运行完整桌面版的 Raspberry Pi OS并直接在 Pi 上使用 [Raspberry Pi OS 版 VS Code](https://code.visualstudio.com/docs/setup/raspberry-pi?WT.mc_id=academic-17441-jabenn) 进行编码,或者将 Pi 作为无头设备运行,并通过 PC 或 Mac 使用 VS Code 的 [Remote SSH 扩展](https://code.visualstudio.com/docs/remote/ssh?WT.mc_id=academic-17441-jabenn) 连接到 Pi像直接在 Pi 上编码一样编辑、调试和运行代码。
如果你选择虚拟设备选项,你将在自己的电脑上直接编码。你将使用一个工具来模拟硬件,提供你定义的传感器值,并在屏幕上显示执行器的结果。
## 设置你的设备
在开始编程你的物联网设备之前,你需要进行一些简单的设置。根据你使用的设备,按照相关说明进行操作。
💁 如果你还没有设备,请参考[硬件指南](../../../hardware.md),帮助你决定将使用哪种设备,以及需要购买哪些额外的硬件。你不需要购买硬件,因为所有项目都可以在虚拟硬件上运行。
这些说明包括了硬件或工具制造商提供的第三方网站链接。这是为了确保您始终使用最新的工具和硬件说明。
按照相关指南设置您的设备并完成一个“Hello World”项目。这是本入门部分四节课中创建物联网夜灯的第一步。
* [Arduino - Wio Terminal](wio-terminal.md)
* [单板计算机 - 树莓派](pi.md)
* [单板计算机 - 虚拟设备](virtual-device.md)
✅ 您将在 Arduino 和单板计算机上使用 VS Code。如果您之前没有使用过可以在 [VS Code 网站](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn)上了解更多信息。
## 物联网的应用
物联网涵盖了广泛的使用场景,主要分为以下几类:
* 消费级物联网
* 商业级物联网
* 工业级物联网
* 基础设施物联网
✅ 做一些研究:对于以下描述的每个领域,找到一个文中未提到的具体例子。
### 消费级物联网
消费级物联网指的是消费者会购买并在家中使用的物联网设备。这些设备中有些非常实用,比如智能音箱、智能供暖系统和扫地机器人。也有一些设备的实用性值得商榷,比如语音控制的水龙头——当水声太大时,语音控制可能无法识别,导致无法关闭水龙头。
消费级物联网设备让人们在日常生活中能够实现更多尤其是全球约10亿有残疾的人群。例如扫地机器人可以帮助行动不便的人保持地板清洁语音控制的烤箱让视力或运动能力有限的人仅通过语音就能加热烤箱健康监测设备可以让患者更频繁、更详细地监控自己的慢性病状况。这些设备已经变得如此普及甚至连小孩子都在日常生活中使用它们。例如在 COVID 疫情期间进行远程学习的学生会用智能家居设备设置计时器来跟踪作业时间,或者设置闹钟提醒即将开始的课程。
✅ 您身上或家中有哪些消费级物联网设备?
### 商业级物联网
商业级物联网涵盖了物联网在工作场所的应用。在办公室环境中,可能会有占用传感器和运动检测器,用于管理照明和供暖,仅在需要时开启灯光和供暖,从而降低成本和碳排放。在工厂中,物联网设备可以监控安全隐患,例如工人未佩戴安全帽或噪音达到危险水平。在零售业中,物联网设备可以监测冷藏设备的温度,如果冰箱或冷柜的温度超出规定范围,会提醒店主;或者监控货架上的商品,指引员工补充已售出的商品。运输行业越来越依赖物联网来监控车辆位置、记录道路行驶里程以便收费、跟踪驾驶员的工作时间和休息合规性,或者在车辆接近仓库时通知工作人员做好装卸准备。
✅ 您的学校或工作场所有哪些商业级物联网设备?
### 工业级物联网 (IIoT)
工业级物联网IIoT是指在大规模机械控制和管理中使用物联网设备。这涵盖了从工厂到数字农业的广泛应用场景。
工厂以多种方式使用物联网设备。通过多个传感器监控机械设备的温度、振动和转速等数据。这些数据可以实时监控当设备超出某些容差范围时例如过热可以自动停止运行。这些数据还可以长期收集并分析用于预测性维护。AI 模型会分析设备故障前的数据,从而预测其他潜在故障。
数字农业对于养活不断增长的人口至关重要尤其是全球约20亿人依赖于[自给农业](https://wikipedia.org/wiki/Subsistence_agriculture)的5亿个家庭。数字农业的成本可以从几美元的传感器到大型商业系统不等。农民可以通过监测温度并使用[生长度日](https://wikipedia.org/wiki/Growing_degree-day)预测作物的收获时间。他们还可以将土壤湿度监测与自动灌溉系统连接起来,确保作物获得所需的水分,同时避免浪费水资源。农民甚至可以进一步利用无人机、卫星数据和 AI 来监测大面积农田的作物生长、病害和土壤质量。
✅ 还有哪些物联网设备可以帮助农民?
### 基础设施物联网
基础设施物联网用于监控和控制人们日常使用的本地和全球基础设施。
[智慧城市](https://wikipedia.org/wiki/Smart_city)是利用物联网设备收集城市数据并改善城市运行的城市。这些城市通常由地方政府、学术界和本地企业合作管理,监控和管理从交通到停车和污染等各种事务。例如,在丹麦哥本哈根,当地居民非常关注空气污染,因此城市会测量空气污染数据,并提供最清洁的骑行和慢跑路线信息。
[智能电网](https://wikipedia.org/wiki/Smart_grid)通过收集家庭级别的用电数据来提供更好的电力需求分析。这些数据可以指导国家层面的决策,例如在哪里建设新的发电站;也可以为个人提供用电量、用电时间的洞察,甚至提供节约成本的建议,例如在夜间为电动车充电。
✅ 如果您可以在居住地添加物联网设备来监测任何事物,您会选择什么?
## 您周围可能存在的物联网设备示例
您可能会惊讶于周围有多少物联网设备。我正在家中写这篇文章,我家中有以下设备连接到互联网,并具有应用程序控制、语音控制或通过手机向我发送数据的功能:
* 多个智能音箱
* 冰箱、洗碗机、烤箱和微波炉
* 太阳能板的电力监测器
* 智能插座
* 视频门铃和安全摄像头
* 带有多个智能房间传感器的智能恒温器
* 车库门开启器
* 家庭娱乐系统和语音控制电视
* 照明设备
* 健身和健康追踪器
所有这些设备都配备了传感器和/或执行器,并与互联网通信。我可以通过手机查看车库门是否打开,并通过智能音箱关闭它。我甚至可以设置定时器,如果晚上车库门仍然打开,它会自动关闭。当门铃响起时,无论我身在何处,我都可以通过手机看到是谁,并通过门铃内置的扬声器和麦克风与他们对话。我可以监测我的血糖、心率和睡眠模式,通过数据模式改善健康状况。我可以通过云端控制灯光,但如果网络连接中断,我可能会坐在黑暗中。
---
## 🚀 挑战
列出您家中、学校或工作场所中的所有物联网设备——可能比您想象的还要多!
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/2)
## 复习与自学
阅读有关消费级物联网项目的优缺点。查看新闻网站上的文章,了解物联网项目失败的案例,例如隐私问题、硬件问题或因缺乏连接性导致的问题。
一些示例:
* 查看 Twitter 账号 **[Internet of Sh*t](https://twitter.com/internetofshit)** *(含粗俗语言警告)*,了解消费级物联网失败的有趣案例。
* [c|net - 我的 Apple Watch 救了我的命5 人分享他们的故事](https://www.cnet.com/news/apple-watch-lifesaving-health-features-read-5-peoples-stories/)
* [c|net - ADT 技术员承认多年来偷窥客户摄像头画面](https://www.cnet.com/news/adt-home-security-technician-pleads-guilty-to-spying-on-customer-camera-feeds-for-years/) *(触发警告 - 非自愿偷窥)*
## 作业
[调查一个物联网项目](assignment.md)
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而导致的任何误解或误读,我们概不负责。

@ -0,0 +1,16 @@
# 调查一个物联网项目
## 说明
全球范围内正在推出许多大大小小的物联网项目,从智能农场到智能城市,涵盖医疗监测、交通运输以及公共空间的使用。
在网上搜索一个你感兴趣的项目的详细信息,最好是离你住的地方较近的项目。解释该项目的优点和缺点,例如它带来的好处、可能引发的问题,以及如何考虑隐私问题。
## 评分标准
| 标准 | 优秀 | 合格 | 需要改进 |
| -------- | --------- | -------- | ----------------- |
| 解释优点和缺点 | 清晰地解释了项目的优点和缺点 | 简要解释了项目的优点和缺点 | 未解释项目的优点或缺点 |
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,275 @@
# 树莓派
[树莓派](https://raspberrypi.org) 是一款单板计算机。你可以通过各种设备和生态系统添加传感器和执行器,在这些课程中,我们将使用一个名为 [Grove](https://www.seeedstudio.com/category/Grove-c-1003.html) 的硬件生态系统。你将使用 Python 编写代码来控制树莓派并访问 Grove 传感器。
![树莓派 4](../../../../../translated_images/zh-CN/raspberry-pi-4.fd4590d308c3d456.webp)
## 设置
如果你使用树莓派作为物联网硬件,有两种选择——你可以直接在树莓派上完成所有课程并编写代码,或者通过远程连接到一个“无头”树莓派,从你的电脑上进行编程。
在开始之前,你还需要将 Grove 基座帽连接到树莓派。
### 任务 - 设置
安装 Grove 基座帽并配置树莓派
1. 将 Grove 基座帽连接到树莓派。基座帽上的插槽与树莓派的所有 GPIO 引脚对接,滑动到底部,牢固地固定在基座上。它覆盖在树莓派上。
![安装 Grove 基座帽](../../../../../images/pi-grove-hat-fitting.gif)
1. 决定如何编程树莓派,然后进入以下相关部分:
* [直接在树莓派上工作](../../../../../1-getting-started/lessons/1-introduction-to-iot)
* [远程访问树莓派进行编程](../../../../../1-getting-started/lessons/1-introduction-to-iot)
### 直接在树莓派上工作
如果你想直接在树莓派上工作,可以使用树莓派 OS 的桌面版本并安装所需的所有工具。
#### 任务 - 直接在树莓派上工作
为开发设置树莓派。
1. 按照 [树莓派设置指南](https://projects.raspberrypi.org/en/projects/raspberry-pi-setting-up) 中的说明设置树莓派,连接键盘/鼠标/显示器,连接到 WiFi 或以太网网络,并更新软件。
为了使用 Grove 传感器和执行器编程树莓派,你需要安装一个编辑器来编写设备代码,以及与 Grove 硬件交互的各种库和工具。
1. 树莓派重启后,点击顶部菜单栏的 **Terminal** 图标启动终端,或者选择 *Menu -> Accessories -> Terminal*
1. 运行以下命令以确保操作系统和已安装的软件是最新的:
```sh
sudo apt update && sudo apt full-upgrade --yes
```
1. 运行以下命令安装 Grove 硬件所需的所有库:
```sh
sudo apt install git python3-dev python3-pip --yes
git clone https://github.com/Seeed-Studio/grove.py
cd grove.py
sudo pip3 install .
sudo raspi-config nonint do_i2c 0
```
这首先安装了 Git 和用于安装 Python 包的 Pip。
Python 的一个强大功能是可以安装 [Pip 包](https://pypi.org)——这些是其他人编写并发布到互联网的代码包。你可以通过一个命令将 Pip 包安装到你的电脑上,然后在代码中使用该包。
Seeed Grove 的 Python 包需要从源码安装。这些命令将克隆包含该包源码的仓库,然后在本地安装。
> 💁 默认情况下,当你安装一个包时,它会在你的电脑上全局可用,这可能会导致包版本问题——例如,一个应用程序依赖于某个版本的包,而另一个应用程序安装新版本时可能会导致问题。为了解决这个问题,你可以使用 [Python 虚拟环境](https://docs.python.org/3/library/venv.html),它本质上是一个专用文件夹中的 Python 副本,当你安装 Pip 包时它们只会安装到该文件夹中。在使用树莓派时你不会使用虚拟环境。Grove 安装脚本会全局安装 Grove 的 Python 包,因此如果你想使用虚拟环境,需要先设置虚拟环境,然后手动重新安装 Grove 包到该环境中。使用全局包更简单,尤其是许多树莓派开发者会为每个项目重新刷写干净的 SD 卡。
最后,这启用了 I<sup>2</sup>C 接口。
1. 使用菜单或在终端中运行以下命令重启树莓派:
```sh
sudo reboot
```
1. 树莓派重启后,重新启动终端并运行以下命令安装 [Visual Studio Code (VS Code)](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn)——这是你将用来用 Python 编写设备代码的编辑器。
```sh
sudo apt install code
```
安装完成后VS Code 可以从顶部菜单中访问。
> 💁 如果你有偏好的工具,可以自由使用任何 Python IDE 或编辑器完成这些课程,但课程中的说明将基于 VS Code。
1. 安装 Pylance。这是 VS Code 的一个扩展,提供 Python 语言支持。参考 [Pylance 扩展文档](https://marketplace.visualstudio.com/items?WT.mc_id=academic-17441-jabenn&itemName=ms-python.vscode-pylance) 了解如何在 VS Code 中安装此扩展。
### 远程访问树莓派进行编程
与直接在树莓派上编程不同,它可以运行“无头”模式,即不连接键盘/鼠标/显示器,并通过你的电脑进行配置和编程,使用 Visual Studio Code。
#### 设置树莓派操作系统
要远程编程,需要在 SD 卡上安装树莓派操作系统。
##### 任务 - 设置树莓派操作系统
设置无头树莓派操作系统。
1. 从 [树莓派操作系统软件页面](https://www.raspberrypi.org/software/) 下载 **树莓派镜像工具** 并安装
1. 将 SD 卡插入你的电脑,必要时使用适配器
1. 启动树莓派镜像工具
1. 在树莓派镜像工具中,选择 **CHOOSE OS** 按钮,然后选择 *Raspberry Pi OS (Other)*,接着选择 *Raspberry Pi OS Lite (32-bit)*
![树莓派镜像工具选择 Raspberry Pi OS Lite](../../../../../translated_images/zh-CN/raspberry-pi-imager.24aedeab9e233d84.webp)
> 💁 Raspberry Pi OS Lite 是树莓派操作系统的一个版本,没有桌面 UI 或基于 UI 的工具。这些对于无头树莓派来说是不需要的,并且使安装更小,启动时间更快。
1. 选择 **CHOOSE STORAGE** 按钮,然后选择你的 SD 卡
1. 按下 `Ctrl+Shift+X` 启动 **高级选项**。这些选项允许在将操作系统写入 SD 卡之前进行一些预配置。
1. 勾选 **Enable SSH** 复选框,并为 `pi` 用户设置密码。这是你稍后登录树莓派时使用的密码。
1. 如果你计划通过 WiFi 连接到树莓派,勾选 **Configure WiFi** 复选框,并输入你的 WiFi SSID 和密码,同时选择你的 WiFi 国家。如果你将使用以太网线,则无需执行此操作。确保你连接的网络与你的电脑在同一个网络中。
1. 勾选 **Set locale settings** 复选框,并设置你的国家和时区
1. 选择 **SAVE** 按钮
1. 选择 **WRITE** 按钮将操作系统写入 SD 卡。如果你使用 macOS系统会要求你输入密码因为写入磁盘镜像的底层工具需要特权访问。
操作系统将被写入 SD 卡,完成后操作系统会弹出 SD 卡,并通知你。将 SD 卡从电脑中取出,插入树莓派,启动树莓派并等待大约 2 分钟让其正常启动。
#### 连接到树莓派
下一步是远程访问树莓派。你可以使用 `ssh` 来完成,这在 macOS、Linux 和最新版本的 Windows 上都可用。
##### 任务 - 连接到树莓派
远程访问树莓派。
1. 启动终端或命令提示符,输入以下命令连接到树莓派:
```sh
ssh pi@raspberrypi.local
```
如果你使用的是旧版本的 Windows没有安装 `ssh`,可以使用 OpenSSH。安装说明可以在 [OpenSSH 安装文档](https://docs.microsoft.com//windows-server/administration/openssh/openssh_install_firstuse?WT.mc_id=academic-17441-jabenn) 中找到。
1. 这将连接到你的树莓派并要求输入密码。
通过 `<hostname>.local` 在网络中找到计算机是 Linux 和 Windows 最近添加的功能。如果你使用 Linux 或 Windows 并收到关于找不到主机名的错误,则需要安装额外的软件以启用 ZeroConf 网络Apple 称为 Bonjour
1. 如果你使用 Linux运行以下命令安装 Avahi
```sh
sudo apt-get install avahi-daemon
```
1. 如果你使用 Windows启用 ZeroConf 的最简单方法是安装 [Bonjour Print Services for Windows](http://support.apple.com/kb/DL999)。你也可以安装 [iTunes for Windows](https://www.apple.com/itunes/download/) 来获取更新版本的工具(独立版本不可用)。
> 💁 如果你无法使用 `raspberrypi.local` 连接,可以使用树莓派的 IP 地址。参考 [树莓派 IP 地址文档](https://www.raspberrypi.org/documentation/remote-access/ip-address.md) 了解获取 IP 地址的多种方法。
1. 输入你在树莓派镜像工具高级选项中设置的密码
#### 配置树莓派上的软件
连接到树莓派后,需要确保操作系统是最新的,并安装与 Grove 硬件交互的各种库和工具。
##### 任务 - 配置树莓派上的软件
配置已安装的树莓派软件并安装 Grove 库。
1. 在你的 `ssh` 会话中,运行以下命令更新并重启树莓派:
```sh
sudo apt update && sudo apt full-upgrade --yes && sudo reboot
```
树莓派将被更新并重启。树莓派重启时,`ssh` 会话将结束,因此等待大约 30 秒后重新连接。
1. 在重新连接的 `ssh` 会话中,运行以下命令安装 Grove 硬件所需的所有库:
```sh
sudo apt install git python3-dev python3-pip --yes
git clone https://github.com/Seeed-Studio/grove.py
cd grove.py
sudo pip3 install .
sudo raspi-config nonint do_i2c 0
```
这首先安装了 Git 和用于安装 Python 包的 Pip。
Python 的一个强大功能是可以安装 [Pip 包](https://pypi.org)——这些是其他人编写并发布到互联网的代码包。你可以通过一个命令将 Pip 包安装到你的电脑上,然后在代码中使用该包。
Seeed Grove 的 Python 包需要从源码安装。这些命令将克隆包含该包源码的仓库,然后在本地安装。
> 💁 默认情况下,当你安装一个包时,它会在你的电脑上全局可用,这可能会导致包版本问题——例如,一个应用程序依赖于某个版本的包,而另一个应用程序安装新版本时可能会导致问题。为了解决这个问题,你可以使用 [Python 虚拟环境](https://docs.python.org/3/library/venv.html),它本质上是一个专用文件夹中的 Python 副本,当你安装 Pip 包时它们只会安装到该文件夹中。在使用树莓派时你不会使用虚拟环境。Grove 安装脚本会全局安装 Grove 的 Python 包,因此如果你想使用虚拟环境,需要先设置虚拟环境,然后手动重新安装 Grove 包到该环境中。使用全局包更简单,尤其是许多树莓派开发者会为每个项目重新刷写干净的 SD 卡。
最后,这启用了 I<sup>2</sup>C 接口。
1. 运行以下命令重启树莓派:
```sh
sudo reboot
```
树莓派重启时,`ssh` 会话将结束。无需重新连接。
#### 配置 VS Code 进行远程访问
树莓派配置完成后,你可以使用 Visual Studio Code (VS Code) 从你的电脑连接到它——这是一个免费的开发文本编辑器,你将用它用 Python 编写设备代码。
##### 任务 - 配置 VS Code 进行远程访问
安装所需软件并远程连接到树莓派。
1. 按照 [VS Code 文档](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) 的说明在你的电脑上安装 VS Code
1. 按照 [VS Code 使用 SSH 进行远程开发文档](https://code.visualstudio.com/docs/remote/ssh?WT.mc_id=academic-17441-jabenn) 的说明安装所需组件
1. 按照相同的说明,将 VS Code 连接到树莓派
1. 连接后,按照 [管理扩展](https://code.visualstudio.com/docs/remote/ssh#_managing-extensions?WT.mc_id=academic-17441-jabenn) 的说明,将 [Pylance 扩展](https://marketplace.visualstudio.com/items?WT.mc_id=academic-17441-jabenn&itemName=ms-python.vscode-pylance) 远程安装到树莓派上
## Hello world
在学习一门新的编程语言或技术时通常会从创建一个“Hello World”应用程序开始。这是一个小型应用程序用于输出类似于 `"Hello World"` 的文本,以验证所有工具是否已正确配置。
针对树莓派的 Hello World 应用程序将确保您正确安装了 Python 和 Visual Studio Code。
这个应用程序将存放在一个名为 `nightlight` 的文件夹中。在本任务的后续部分中,我们会在此基础上添加不同的代码,最终构建一个夜灯应用程序。
### 任务 - Hello World
创建 Hello World 应用程序。
1. 启动 VS Code可以直接在树莓派上运行也可以通过 Remote SSH 扩展在您的电脑上连接到树莓派运行。
1. 通过选择 *Terminal -> New Terminal* 或按下 `` CTRL+` `` 打开 VS Code 的终端。终端会默认打开到 `pi` 用户的主目录。
1. 运行以下命令,为您的代码创建一个目录,并在该目录中创建一个名为 `app.py` 的 Python 文件:
```sh
mkdir nightlight
cd nightlight
touch app.py
```
1. 在 VS Code 中打开这个文件夹,选择 *File -> Open...*,然后选择 *nightlight* 文件夹,最后点击 **OK**
![VS Code 的打开对话框显示了 nightlight 文件夹](../../../../../translated_images/zh-CN/vscode-open-nightlight-remote.d3d2a4011e30d535.webp)
1. 从 VS Code 的资源管理器中打开 `app.py` 文件,并添加以下代码:
```python
print('Hello World!')
```
`print` 函数会将传递给它的内容打印到控制台。
1. 在 VS Code 的终端中运行以下命令来运行您的 Python 应用程序:
```sh
python app.py
```
> 💁 如果您的设备同时安装了 Python 2 和 Python 3可能需要显式调用 `python3` 来运行此代码。如果调用 `python`,系统可能会默认使用 Python 2 而不是 Python 3。默认情况下最新版本的树莓派操作系统只安装了 Python 3。
终端中会显示以下输出:
```output
pi@raspberrypi:~/nightlight $ python3 app.py
Hello World!
```
> 💁 您可以在 [code/pi](../../../../../1-getting-started/lessons/1-introduction-to-iot/code/pi) 文件夹中找到这段代码。
😀 恭喜!您的 'Hello World' 程序运行成功!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于重要信息,建议使用专业人工翻译。我们对因使用此翻译而产生的任何误解或误读不承担责任。

@ -0,0 +1,234 @@
# 虚拟单板计算机
与其购买物联网设备以及传感器和执行器,不如使用您的计算机来模拟物联网硬件。[CounterFit 项目](https://github.com/CounterFit-IoT/CounterFit) 允许您在本地运行一个应用程序,该应用程序可以模拟传感器和执行器等物联网硬件,并通过本地 Python 代码访问这些传感器和执行器。这些代码的编写方式与您在 Raspberry Pi 上使用物理硬件时的代码相同。
## 设置
要使用 CounterFit您需要在计算机上安装一些免费软件。
### 任务
安装所需的软件。
1. 安装 Python。请参考 [Python 下载页面](https://www.python.org/downloads/) 了解如何安装最新版本的 Python。
1. 安装 Visual Studio Code (VS Code)。这是您将用来编写虚拟设备 Python 代码的编辑器。请参考 [VS Code 文档](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) 了解如何安装 VS Code。
> 💁 如果您有偏好的工具,可以自由选择任何 Python IDE 或编辑器来完成这些课程,但课程将基于使用 VS Code 的说明。
1. 安装 VS Code 的 Pylance 扩展。这是一个为 VS Code 提供 Python 语言支持的扩展。请参考 [Pylance 扩展文档](https://marketplace.visualstudio.com/items?WT.mc_id=academic-17441-jabenn&itemName=ms-python.vscode-pylance) 了解如何在 VS Code 中安装此扩展。
安装和配置 CounterFit 应用程序的说明将在作业说明的相关时间给出,因为它是按项目安装的。
## Hello World
在学习一种新的编程语言或技术时,通常会创建一个 "Hello World" 应用程序——一个小型应用程序,输出类似 `"Hello World"` 的文本,以显示所有工具已正确配置。
虚拟物联网硬件的 Hello World 应用程序将确保您正确安装了 Python 和 Visual Studio Code。它还将连接到 CounterFit用于虚拟物联网传感器和执行器。它不会使用任何硬件只是连接以证明一切正常。
此应用程序将位于名为 `nightlight` 的文件夹中,并将在作业的后续部分中重复使用不同的代码来构建夜灯应用程序。
### 配置 Python 虚拟环境
Python 的一个强大功能是能够安装 [Pip 包](https://pypi.org)——这些是由其他人编写并发布到互联网的代码包。您可以通过一个命令在计算机上安装一个 Pip 包,然后在代码中使用该包。您将使用 Pip 安装一个包来与 CounterFit 通信。
默认情况下,当您安装一个包时,它在计算机上的任何地方都可用,这可能会导致包版本问题——例如,一个应用程序依赖于一个包的版本,而另一个应用程序安装新版本时可能会导致问题。为了解决这个问题,您可以使用 [Python 虚拟环境](https://docs.python.org/3/library/venv.html),它本质上是一个专用文件夹中的 Python 副本,当您安装 Pip 包时,它们只会安装到该文件夹中。
> 💁 如果您使用的是 Raspberry Pi那么您没有在该设备上设置虚拟环境来管理 Pip 包,而是使用全局包,因为 Grove 包是通过安装脚本全局安装的。
#### 任务 - 配置 Python 虚拟环境
配置 Python 虚拟环境并安装 CounterFit 的 Pip 包。
1. 在终端或命令行中,在您选择的位置运行以下命令以创建并导航到一个新目录:
```sh
mkdir nightlight
cd nightlight
```
1. 现在运行以下命令,在 `.venv` 文件夹中创建一个虚拟环境:
```sh
python3 -m venv .venv
```
> 💁 您需要显式调用 `python3` 来创建虚拟环境,以防您同时安装了 Python 2 和 Python 3最新版本。如果您安装了 Python 2那么调用 `python` 将使用 Python 2 而不是 Python 3。
1. 激活虚拟环境:
* 在 Windows 上:
* 如果您使用的是命令提示符或通过 Windows Terminal 使用命令提示符,请运行:
```cmd
.venv\Scripts\activate.bat
```
* 如果您使用的是 PowerShell请运行
```powershell
.\.venv\Scripts\Activate.ps1
```
> 如果您收到关于系统禁用脚本运行的错误,您需要通过设置适当的执行策略来启用脚本运行。您可以以管理员身份启动 PowerShell然后运行以下命令
```powershell
Set-ExecutionPolicy -ExecutionPolicy Unrestricted
```
当被要求确认时输入 `Y`。然后重新启动 PowerShell 并重试。
如果需要,您可以稍后重置此执行策略。您可以在 [Microsoft Docs 的执行策略页面](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_execution_policies?WT.mc_id=academic-17441-jabenn) 上阅读更多相关信息。
* 在 macOS 或 Linux 上,运行:
```cmd
source ./.venv/bin/activate
```
> 💁 这些命令应在您运行创建虚拟环境命令的同一位置运行。您永远不需要导航到 `.venv` 文件夹,您应该始终从创建虚拟环境时所在的文件夹运行激活命令以及任何安装包或运行代码的命令。
1. 激活虚拟环境后,默认的 `python` 命令将运行用于创建虚拟环境的 Python 版本。运行以下命令以获取版本:
```sh
python --version
```
输出应包含以下内容:
```output
(.venv) ➜ nightlight python --version
Python 3.9.1
```
> 💁 您的 Python 版本可能不同——只要是 3.6 或更高版本即可。如果不是,请删除此文件夹,安装更新版本的 Python然后重试。
1. 运行以下命令以安装 CounterFit 的 Pip 包。这些包包括主要的 CounterFit 应用程序以及 Grove 硬件的 shims。这些 shims 允许您像编写使用 Grove 生态系统中的物理传感器和执行器的代码一样编写代码,但连接到虚拟物联网设备。
```sh
pip install CounterFit
pip install counterfit-connection
pip install counterfit-shims-grove
```
这些 Pip 包将仅安装在虚拟环境中,无法在其外部使用。
### 编写代码
准备好 Python 虚拟环境后,您可以编写 "Hello World" 应用程序的代码。
#### 任务 - 编写代码
创建一个 Python 应用程序,在控制台打印 `"Hello World"`
1. 在虚拟环境中,从终端或命令行运行以下命令以创建一个名为 `app.py` 的 Python 文件:
* 在 Windows 上运行:
```cmd
type nul > app.py
```
* 在 macOS 或 Linux 上运行:
```cmd
touch app.py
```
1. 在 VS Code 中打开当前文件夹:
```sh
code .
```
> 💁 如果您的终端在 macOS 上返回 `command not found`,这意味着 VS Code 尚未添加到您的 PATH。您可以通过遵循 [VS Code 文档中从命令行启动部分](https://code.visualstudio.com/docs/setup/mac?WT.mc_id=academic-17441-jabenn#_launching-from-the-command-line) 的说明将 VS Code 添加到 PATH然后运行命令。VS Code 在 Windows 和 Linux 上默认安装到 PATH。
1. 当 VS Code 启动时,它将激活 Python 虚拟环境。选定的虚拟环境将显示在底部状态栏中:
![VS Code 显示选定的虚拟环境](../../../../../translated_images/zh-CN/vscode-virtual-env.8ba42e04c3d533cf.webp)
1. 如果 VS Code Terminal 在 VS Code 启动时已经运行,它将不会激活虚拟环境。最简单的方法是使用 **Kill the active terminal instance** 按钮关闭终端:
![VS Code Kill the active terminal instance 按钮](../../../../../translated_images/zh-CN/vscode-kill-terminal.1cc4de7c6f25ee08.webp)
您可以通过终端提示的前缀来判断终端是否激活了虚拟环境。例如,它可能是:
```sh
(.venv) ➜ nightlight
```
如果提示中没有 `.venv` 作为前缀,则终端未激活虚拟环境。
1. 通过选择 *Terminal -> New Terminal* 或按 `` CTRL+` `` 启动一个新的 VS Code Terminal。新终端将加载虚拟环境并在终端中显示激活此环境的调用。提示也会带有虚拟环境的名称 (`.venv`)
```output
➜ nightlight source .venv/bin/activate
(.venv) ➜ nightlight
```
1. 从 VS Code 的资源管理器中打开 `app.py` 文件,并添加以下代码:
```python
print('Hello World!')
```
`print` 函数会将传递给它的内容打印到控制台。
1. 从 VS Code 的终端运行以下命令以运行您的 Python 应用程序:
```sh
python app.py
```
输出将包含以下内容:
```output
(.venv) ➜ nightlight python app.py
Hello World!
```
😀 您的 "Hello World" 程序运行成功!
### 连接“硬件”
作为第二个 "Hello World" 步骤,您将运行 CounterFit 应用程序并将代码连接到它。这是将一些物联网硬件插入开发套件的虚拟等效操作。
#### 任务 - 连接“硬件”
1. 从 VS Code 的终端运行以下命令启动 CounterFit 应用程序:
```sh
counterfit
```
应用程序将开始运行并在您的浏览器中打开:
![在浏览器中运行的 Counter Fit 应用程序](../../../../../translated_images/zh-CN/counterfit-first-run.433326358b669b31d0e99c3513cb01bfbb13724d162c99cdcc8f51ecf5f9c779.png)
它将显示为 *Disconnected*,右上角的 LED 将关闭。
1. 在 `app.py` 顶部添加以下代码:
```python
from counterfit_connection import CounterFitConnection
CounterFitConnection.init('127.0.0.1', 5000)
```
此代码从您之前安装的 `counterfit-connection` pip 包中导入 `CounterFitConnection` 类。然后它初始化一个连接到运行在 `127.0.0.1` 上的 CounterFit 应用程序,这是一个可以始终用于访问本地计算机的 IP 地址(通常称为 *localhost*),端口为 5000。
> 💁 如果您有其他应用程序运行在端口 5000您可以通过更新代码中的端口并使用 `CounterFit --port <port_number>` 运行 CounterFit 来更改此端口,将 `<port_number>` 替换为您想使用的端口。
1. 您需要通过选择 **Create a new integrated terminal** 按钮启动一个新的 VS Code 终端。这是因为 CounterFit 应用程序正在当前终端中运行。
![VS Code Create a new integrated terminal 按钮](../../../../../translated_images/zh-CN/vscode-new-terminal.77db8fc0f9cd3182.webp)
1. 在这个新终端中,像之前一样运行 `app.py` 文件。CounterFit 的状态将变为 **Connected**LED 将亮起。
![Counter Fit 显示为已连接](../../../../../translated_images/zh-CN/counterfit-connected.ed30b46d8f79b0921f3fc70be10366e596a89dca3f80c2224a9d9fc98fccf884.png)
> 💁 您可以在 [code/virtual-device](../../../../../1-getting-started/lessons/1-introduction-to-iot/code/virtual-device) 文件夹中找到此代码。
😀 您成功连接到硬件!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们不对因使用此翻译而产生的任何误解或误读承担责任。

@ -0,0 +1,211 @@
# Wio Terminal
[Seeed Studios 的 Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html) 是一款兼容 Arduino 的微控制器,内置 WiFi 和一些传感器及执行器,同时还提供了端口,可以通过名为 [Grove](https://www.seeedstudio.com/category/Grove-c-1003.html) 的硬件生态系统添加更多传感器和执行器。
![Seeed Studios 的 Wio Terminal](../../../../../translated_images/zh-CN/wio-terminal.b8299ee16587db9a.webp)
## 设置
要使用 Wio Terminal您需要在计算机上安装一些免费的软件。此外在将其连接到 WiFi 之前,您还需要更新 Wio Terminal 的固件。
### 任务 - 设置
安装所需的软件并更新固件。
1. 安装 Visual Studio Code (VS Code)。这是您将用来用 C/C++ 编写设备代码的编辑器。请参考 [VS Code 文档](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) 了解安装 VS Code 的说明。
> 💁 另一个用于 Arduino 开发的流行 IDE 是 [Arduino IDE](https://www.arduino.cc/en/software)。如果您已经熟悉这个工具,那么可以选择使用它而不是 VS Code 和 PlatformIO但课程将基于使用 VS Code 的说明。
1. 安装 VS Code 的 PlatformIO 扩展。这是一个支持用 C/C++ 编程微控制器的 VS Code 扩展。请参考 [PlatformIO 扩展文档](https://marketplace.visualstudio.com/items?WT.mc_id=academic-17441-jabenn&itemName=platformio.platformio-ide) 了解如何在 VS Code 中安装此扩展。此扩展依赖于 Microsoft 的 C/C++ 扩展,安装 PlatformIO 时会自动安装 C/C++ 扩展。
1. 将 Wio Terminal 连接到您的计算机。Wio Terminal 底部有一个 USB-C 接口,需要将其连接到计算机的 USB 接口。Wio Terminal 附带一根 USB-C 到 USB-A 的数据线,但如果您的计算机只有 USB-C 接口,则需要一根 USB-C 数据线或 USB-A 转 USB-C 转接头。
1. 按照 [Wio Terminal Wiki WiFi 概述文档](https://wiki.seeedstudio.com/Wio-Terminal-Network-Overview/) 中的说明设置 Wio Terminal 并更新固件。
## Hello World
在学习一种新的编程语言或技术时,通常会创建一个 "Hello World" 应用程序——一个小型应用程序,用于输出类似 `"Hello World"` 的文本,以验证所有工具是否已正确配置。
Wio Terminal 的 Hello World 应用程序将确保您已正确安装 Visual Studio Code 和 PlatformIO并完成微控制器开发的设置。
### 创建一个 PlatformIO 项目
第一步是使用为 Wio Terminal 配置的 PlatformIO 创建一个新项目。
#### 任务 - 创建一个 PlatformIO 项目
创建 PlatformIO 项目。
1. 将 Wio Terminal 连接到您的计算机
1. 启动 VS Code
1. 在侧边菜单栏中找到 PlatformIO 图标:
![PlatformIO 菜单选项](../../../../../translated_images/zh-CN/vscode-platformio-menu.297be26b9733e5c4.webp)
选择此菜单项,然后选择 *PIO Home -> Open*
![PlatformIO 打开选项](../../../../../translated_images/zh-CN/vscode-platformio-home-open.3f9a41bfd3f4da1c.webp)
1. 在欢迎界面中,选择 **+ New Project** 按钮
![新建项目按钮](../../../../../translated_images/zh-CN/vscode-platformio-welcome-new-button.ba6fc8a4c7b78cc8.webp)
1. 在 *Project Wizard* 中配置项目:
1. 将项目命名为 `nightlight`
1. 在 *Board* 下拉菜单中输入 `WIO` 以筛选板卡,选择 *Seeeduino Wio Terminal*
1. 将 *Framework* 保持为 *Arduino*
1. 可以保持勾选 *Use default location* 复选框,或者取消勾选并选择项目的保存位置
1. 选择 **Finish** 按钮
![完成的项目向导](../../../../../translated_images/zh-CN/vscode-platformio-nightlight-project-wizard.5c64db4da6037420.webp)
PlatformIO 将下载编译 Wio Terminal 代码所需的组件并创建您的项目。这可能需要几分钟。
### 查看 PlatformIO 项目
VS Code 的资源管理器将显示由 PlatformIO 向导创建的多个文件和文件夹。
#### 文件夹
* `.pio` - 此文件夹包含 PlatformIO 所需的临时数据,例如库或编译后的代码。如果删除,它会自动重新创建。如果您将项目共享到 GitHub 等网站,则无需将此文件夹添加到源代码管理中。
* `.vscode` - 此文件夹包含 PlatformIO 和 VS Code 的配置文件。如果删除,它会自动重新创建。如果您将项目共享到 GitHub 等网站,则无需将此文件夹添加到源代码管理中。
* `include` - 此文件夹用于存放在添加额外库时需要的外部头文件。在这些课程中,您不会使用此文件夹。
* `lib` - 此文件夹用于存放您希望从代码中调用的外部库。在这些课程中,您不会使用此文件夹。
* `src` - 此文件夹包含应用程序的主要源代码。最初,它只包含一个文件——`main.cpp`。
* `test` - 此文件夹用于存放代码的单元测试。
#### 文件
* `main.cpp` - `src` 文件夹中的此文件包含应用程序的入口点。打开此文件,您会看到以下代码:
```cpp
#include <Arduino.h>
void setup() {
// put your setup code here, to run once:
}
void loop() {
// put your main code here, to run repeatedly:
}
```
当设备启动时Arduino 框架会运行一次 `setup` 函数,然后重复运行 `loop` 函数,直到设备关闭。
* `.gitignore` - 此文件列出了在将代码添加到 git 源代码管理(例如上传到 GitHub 仓库)时需要忽略的文件和目录。
* `platformio.ini` - 此文件包含设备和应用程序的配置。打开此文件,您会看到以下代码:
```ini
[env:seeed_wio_terminal]
platform = atmelsam
board = seeed_wio_terminal
framework = arduino
```
`[env:seeed_wio_terminal]` 部分包含 Wio Terminal 的配置。您可以有多个 `env` 部分,以便为多个板卡编译代码。
其他值与项目向导中的配置相匹配:
* `platform = atmelsam` 定义了 Wio Terminal 使用的硬件(基于 ATSAMD51 的微控制器)
* `board = seeed_wio_terminal` 定义了微控制器板卡的类型Wio Terminal
* `framework = arduino` 定义了此项目使用 Arduino 框架。
### 编写 Hello World 应用程序
现在您可以开始编写 Hello World 应用程序了。
#### 任务 - 编写 Hello World 应用程序
编写 Hello World 应用程序。
1. 在 VS Code 中打开 `main.cpp` 文件
1. 将代码更改为以下内容:
```cpp
#include <Arduino.h>
void setup()
{
Serial.begin(9600);
while (!Serial)
; // Wait for Serial to be ready
delay(1000);
}
void loop()
{
Serial.println("Hello World");
delay(5000);
}
```
`setup` 函数初始化与串口的连接——在本例中是用于连接 Wio Terminal 和计算机的 USB 接口。参数 `9600` 是 [波特率](https://wikipedia.org/wiki/Symbol_rate)也称为符号率即数据通过串口传输的速度单位是每秒比特数bps。此设置表示每秒传输 9,600 比特0 和 1。然后它等待串口准备就绪。
`loop` 函数将字符串 `Hello World!` 发送到串口,同时附加一个换行符。然后,它会暂停 5,000 毫秒5 秒)。`loop` 函数结束后会再次运行,如此循环,直到微控制器断电。
1. 将 Wio Terminal 设置为上传模式。每次上传新代码到设备时都需要这样做:
1. 快速向下拨动电源开关两次——每次都会弹回到开启位置。
1. 检查 USB 接口右侧的蓝色状态 LED。它应该呈现脉冲状闪烁。
[![如何将 Wio Terminal 设置为上传模式的视频](https://img.youtube.com/vi/LeKU_7zLRrQ/0.jpg)](https://youtu.be/LeKU_7zLRrQ)
点击上方图片观看操作视频。
1. 编译并上传代码到 Wio Terminal
1. 打开 VS Code 的命令面板
1. 输入 `PlatformIO Upload` 搜索上传选项,选择 *PlatformIO: Upload*
![命令面板中的 PlatformIO 上传选项](../../../../../translated_images/zh-CN/vscode-platformio-upload-command-palette.9e0f49cf80d1f1c3.webp)
如果需要PlatformIO 会自动编译代码,然后上传。
1. 代码将被编译并上传到 Wio Terminal
> 💁 如果您使用的是 macOS可能会出现 *DISK NOT EJECTED PROPERLY* 的通知。这是因为在闪存过程中Wio Terminal 会被挂载为一个驱动器,当编译后的代码写入设备时会断开连接。您可以忽略此通知。
⚠️ 如果出现上传端口不可用的错误,首先确保 Wio Terminal 已连接到计算机,并通过屏幕左侧的开关打开电源并设置为上传模式。底部的绿灯应亮起,蓝灯应呈现脉冲状闪烁。如果仍然出现错误,请快速向下拨动电源开关两次以强制 Wio Terminal 进入上传模式,然后重试上传。
PlatformIO 提供了一个串口监视器,可以通过 USB 数据线监视从 Wio Terminal 发送的数据。这使您可以监视 `Serial.println("Hello World");` 命令发送的数据。
1. 打开 VS Code 的命令面板
1. 输入 `PlatformIO Serial` 搜索串口监视器选项,选择 *PlatformIO: Serial Monitor*
![命令面板中的 PlatformIO 串口监视器选项](../../../../../translated_images/zh-CN/vscode-platformio-serial-monitor-command-palette.b348ec841b8a1c14.webp)
一个新终端将打开,串口发送的数据将流入此终端:
```output
> Executing task: platformio device monitor <
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at http://bit.ly/pio-monitor-filters
--- Miniterm on /dev/cu.usbmodem101 9600,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
Hello World
Hello World
```
`Hello World` 将每 5 秒打印一次到串口监视器。
> 💁 您可以在 [code/wio-terminal](../../../../../1-getting-started/lessons/1-introduction-to-iot/code/wio-terminal) 文件夹中找到此代码。
😀 恭喜!您的 "Hello World" 程序运行成功!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于重要信息,建议使用专业人工翻译。我们不对因使用此翻译而产生的任何误解或误读承担责任。

@ -0,0 +1,266 @@
# 深入了解物联网
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-2.324b0580d620c25e0a24fb7fddfc0b29a846dd4b82c08e7a9466d580ee78ce51.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看更大版本。
本课程是 [Microsoft Reactor](https://developer.microsoft.com/reactor/?WT.mc_id=academic-17441-jabenn) 的 [Hello IoT 系列](https://youtube.com/playlist?list=PLmsFUfdnGr3xRts0TIwyaHyQuHaNQcb6-) 的一部分。课程分为两段视频讲授——一个1小时的课程和一个1小时的答疑环节深入探讨课程内容并回答问题。
[![课程2深入了解物联网](https://img.youtube.com/vi/t0SySWw3z9M/0.jpg)](https://youtu.be/t0SySWw3z9M)
[![课程2深入了解物联网 - 答疑环节](https://img.youtube.com/vi/tTZYf9EST1E/0.jpg)](https://youtu.be/tTZYf9EST1E)
> 🎥 点击上方图片观看视频
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/3)
## 简介
本课程将深入探讨上一课中介绍的一些概念。
在本课程中,我们将学习:
* [物联网应用的组成部分](../../../../../1-getting-started/lessons/2-deeper-dive)
* [深入了解微控制器](../../../../../1-getting-started/lessons/2-deeper-dive)
* [深入了解单板计算机](../../../../../1-getting-started/lessons/2-deeper-dive)
## 物联网应用的组成部分
物联网应用的两个组成部分是 *互联网**设备*。让我们更详细地了解这两个部分。
### 设备
![树莓派 4](../../../../../translated_images/zh-CN/raspberry-pi-4.fd4590d308c3d456.webp)
物联网中的 **设备** 指的是能够与物理世界交互的设备。这些设备通常是小型、低成本的计算机,运行速度较低且功耗较低——例如,简单的微控制器,只有几千字节的内存(而不是 PC 的几千兆字节),运行速度只有几百兆赫兹(而不是 PC 的几千兆赫兹),但功耗极低,有时可以用电池运行数周、数月甚至数年。
这些设备通过传感器从周围环境中收集数据,或者通过控制输出或执行器来进行物理改变,从而与物理世界交互。一个典型的例子是智能恒温器——一种具有温度传感器、设置目标温度的方式(如旋钮或触摸屏)以及连接到加热或冷却系统的设备。当检测到的温度超出目标范围时,它可以打开加热或冷却系统。温度传感器检测到房间太冷,执行器则打开加热系统。
![一个图示显示温度和旋钮作为物联网设备的输入,控制加热器作为输出](../../../../../translated_images/zh-CN/basic-thermostat.a923217fd1f37e5a6f3390396a65c22a387419ea2dd17e518ec24315ba6ae9a8.png)
可以充当物联网设备的“设备”种类繁多,从专用硬件到通用设备,甚至包括你的智能手机!智能手机可以使用传感器检测周围环境,并使用执行器与世界交互——例如,使用 GPS 传感器检测位置,并通过扬声器提供导航指令。
✅ 想想你周围还有哪些系统可以通过传感器读取数据并据此做出决策。例如,烤箱上的恒温器就是一个例子。你还能找到更多吗?
### 互联网
物联网应用中的 **互联网** 部分包括物联网设备可以连接以发送和接收数据的应用程序,以及可以处理物联网设备数据并帮助做出决策的其他应用程序。
一个典型的设置是物联网设备连接到某种云服务,该云服务处理安全性、接收来自物联网设备的消息并将消息发送回设备。然后,这个云服务连接到其他应用程序,这些应用程序可以处理或存储传感器数据,或者结合其他系统的数据来做出决策。
设备并不总是通过 WiFi 或有线连接直接连接到互联网。有些设备使用网状网络,通过蓝牙等技术相互通信,并通过一个具有互联网连接的集线器设备进行连接。
以智能恒温器为例,恒温器通过家庭 WiFi 连接到云服务。它将温度数据发送到云服务,云服务将数据写入某种数据库,允许房主通过手机应用查看当前和过去的温度。云中的另一个服务知道房主想要的温度,并通过云服务将消息发送回物联网设备,告诉加热系统打开或关闭。
![一个图示显示温度和旋钮作为物联网设备的输入,物联网设备与云之间的双向通信,云与手机之间的双向通信,以及加热器作为物联网设备的输出](../../../../../translated_images/zh-CN/mobile-controlled-thermostat.4a994010473d8d6a52ba68c67e5f02dc8928c717e93ca4b9bc55525aa75bbb60.png)
更智能的版本可以使用云中的 AI结合其他物联网设备如占用传感器连接的其他传感器数据以及天气和日历等数据智能地设置温度。例如它可以读取你的日历显示你正在度假时关闭加热或者根据你使用的房间逐个关闭加热并从数据中学习以变得越来越准确。
![一个图示显示多个温度传感器和旋钮作为物联网设备的输入,物联网设备与云之间的双向通信,云与手机、日历和天气服务之间的双向通信,以及加热器作为物联网设备的输出](../../../../../translated_images/zh-CN/smarter-thermostat.a75855f15d2d9e63.webp)
✅ 还有哪些数据可以帮助使联网恒温器变得更智能?
### 边缘上的物联网
尽管物联网中的 I 代表互联网,但这些设备并不一定要连接到互联网。在某些情况下,设备可以连接到“边缘”设备——运行在本地网络上的网关设备,这意味着可以在不通过互联网的情况下处理数据。这在数据量大或互联网连接较慢时会更快,允许在无法连接互联网的情况下运行,例如在船上或在响应人道主义危机的灾区,并且可以保持数据隐私。一些设备会包含使用云工具创建的处理代码,并在本地运行以收集和响应数据,而无需通过互联网连接来做出决策。
一个例子是智能家居设备,例如 Apple HomePod、Amazon Alexa 或 Google Home它们使用在云中训练的 AI 模型来监听你的语音,但在设备上本地运行。这些设备在听到某个词或短语时会“唤醒”,然后将你的语音发送到互联网进行处理。设备会在检测到语音中的停顿时停止发送语音。在唤醒设备之前说的所有内容,以及设备停止监听后说的所有内容,都不会发送到设备提供商的互联网,因此是私密的。
✅ 想想还有哪些场景隐私很重要,因此数据处理最好在边缘而不是云中进行。提示:想想带有摄像头或其他成像设备的物联网设备。
### 物联网安全
任何互联网连接都需要考虑安全性。有一个老笑话说“物联网中的 S 代表安全”——物联网中没有“S”暗示它不安全。
物联网设备连接到云服务,因此它的安全性取决于云服务的安全性——如果你的云服务允许任何设备连接,那么可能会发送恶意数据或发生病毒攻击。这可能会产生非常现实的后果,因为物联网设备会与其他设备交互并进行控制。例如,[Stuxnet 蠕虫](https://wikipedia.org/wiki/Stuxnet) 操纵离心机中的阀门以损坏它们。黑客还利用[安全性差访问婴儿监视器](https://www.npr.org/sections/thetwo-way/2018/06/05/617196788/s-c-mom-says-baby-monitor-was-hacked-experts-say-many-devices-are-vulnerable)和其他家庭监控设备。
> 💁 有时物联网设备和边缘设备运行在完全与互联网隔离的网络上,以保持数据的隐私和安全。这被称为[隔离网络](https://wikipedia.org/wiki/Air_gap_(networking))。
## 深入了解微控制器
在上一课中,我们介绍了微控制器。现在让我们更深入地了解它们。
### CPU
CPU 是微控制器的“核心”。它是运行代码的处理器可以向任何连接的设备发送数据并接收数据。CPU 可以包含一个或多个核心——本质上是一个或多个可以协同工作运行代码的 CPU。
CPU 依赖时钟以每秒数百万或数十亿次的频率进行计时。每次计时或周期都会同步 CPU 可以执行的操作。每次计时CPU 可以执行程序中的一条指令,例如从外部设备检索数据或执行数学计算。这种规律的周期允许所有操作在处理下一条指令之前完成。
时钟周期越快,每秒可以处理的指令越多,因此 CPU 的速度越快。CPU 的速度以[赫兹 (Hz)](https://wikipedia.org/wiki/Hertz)为单位测量1 Hz 表示每秒一个周期或时钟计时。
> 🎓 CPU 速度通常以 MHz 或 GHz 表示。1MHz 是 1 百万 Hz1GHz 是 1 十亿 Hz。
> 💁 CPU 使用[取指-译码-执行周期](https://wikipedia.org/wiki/Instruction_cycle)执行程序。对于每次时钟计时CPU 会从内存中取指令,译码,然后执行,例如使用算术逻辑单元 (ALU) 加两个数字。一些执行需要多个计时才能完成,因此下一周期将在指令完成后的下一次计时运行。
![取指-译码-执行周期图示,显示取指从存储在 RAM 中的程序中获取指令,然后在 CPU 上译码和执行](../../../../../translated_images/zh-CN/fetch-decode-execute.2fd6f150f6280392807f4475382319abd0cee0b90058e1735444d6baa6f2078c.png)
微控制器的时钟速度远低于台式机或笔记本电脑甚至大多数智能手机。例如Wio Terminal 的 CPU 运行速度为 120MHz即每秒 120,000,000 次周期。
✅ 一台普通的 PC 或 Mac 的 CPU 有多个核心,运行速度为多个 GHz即时钟每秒计时数十亿次。研究一下你电脑的时钟速度并与 Wio Terminal 的速度进行比较。
每次时钟周期都会消耗电力并产生热量。计时越快消耗的电力越多产生的热量也越多。PC 有散热片和风扇来散热否则它们会在几秒钟内过热并关闭。微控制器通常没有这些因为它们运行得更凉因此速度更慢。PC 使用主电源或大电池运行几个小时,而微控制器可以使用小电池运行数天、数月甚至数年。微控制器还可以有以不同速度运行的核心,当 CPU 的需求较低时切换到较慢的低功耗核心以减少功耗。
> 💁 一些 PC 和 Mac 也采用了这种快速高功耗核心和较慢低功耗核心的组合,根据运行的任务在电池寿命和速度之间切换。例如,最新 Apple 笔记本电脑中的 M1 芯片可以在 4 个性能核心和 4 个效率核心之间切换,以根据运行的任务优化电池寿命或速度。
✅ 做一些研究:阅读[Wikipedia CPU 文章](https://wikipedia.org/wiki/Central_processing_unit)了解更多关于 CPU 的信息。
#### 任务
调查 Wio Terminal。
如果你正在使用 Wio Terminal 学习这些课程,试着找到 CPU。在 [Wio Terminal 产品页面](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)的 *硬件概览* 部分找到内部图片,并通过背面的透明塑料窗口找到 CPU。
### 内存
微控制器通常有两种类型的内存——程序内存和随机存取内存 (RAM)。
程序内存是非易失性的,这意味着写入其中的内容在设备断电时仍然保留。这是存储程序代码的内存。
RAM 是程序运行时使用的内存包含程序分配的变量和从外设收集的数据。RAM 是易失性的,当断电时内容会丢失,程序会被有效地重置。
🎓 程序存储器保存您的代码,即使断电也会保留。
🎓 RAM用于运行程序当断电时会被重置。
与CPU类似微控制器上的内存比PC或Mac小得多。一个典型的PC可能有8GB8,000,000,000字节的RAM每个字节足够存储一个字母或0-255之间的一个数字。而微控制器通常只有千字节KB的RAM一个千字节是1,000字节。上面提到的Wio Terminal有192KB的RAM即192,000字节——比普通PC少了超过40,000倍
下图展示了192KB与8GB之间的相对大小差异——中心的小点代表192KB。
![192KB与8GB的比较——8GB大约是192KB的40,000倍](../../../../../translated_images/zh-CN/ram-comparison.6beb73541b42ac6f.webp)
程序存储空间也比PC小。一个典型的PC可能有500GB的硬盘用于存储程序而微控制器可能只有几千字节或几兆字节MB的存储空间1MB是1,000KB或1,000,000字节。Wio Terminal有4MB的程序存储空间。
✅ 做一些研究你正在使用的电脑有多少RAM和存储空间与微控制器相比如何
### 输入/输出
微控制器需要输入和输出I/O连接来从传感器读取数据并向执行器发送控制信号。它们通常包含一些通用输入/输出GPIO引脚。这些引脚可以通过软件配置为输入接收信号或输出发送信号
🧠⬅️ 输入引脚用于从传感器读取值。
🧠➡️ 输出引脚向执行器发送指令。
✅ 你将在后续课程中学习更多相关内容。
#### 任务
研究Wio Terminal。
如果你在这些课程中使用Wio Terminal请找到GPIO引脚。在[Wio Terminal产品页面](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)的*引脚图*部分了解每个引脚的功能。Wio Terminal附带一个可以贴在背面的贴纸上面标有引脚编号如果还没贴上请现在贴上。
### 物理尺寸
微控制器通常体积很小,最小的一个[Freescale Kinetis KL03 MCU可以小到放进高尔夫球的凹槽里](https://www.edn.com/tiny-arm-cortex-m0-based-mcu-shrinks-package/)。而PC中的CPU可能有40mm x 40mm的尺寸这还不包括为了防止过热而需要的散热器和风扇这使得CPU的尺寸远大于一个完整的微控制器。带有微控制器、外壳、屏幕以及一系列连接和组件的Wio Terminal开发套件的体积甚至比裸露的Intel i9 CPU还要小更不用说加上散热器和风扇后的尺寸了
| 设备 | 尺寸 |
| ------------------------------ | --------------------- |
| Freescale Kinetis KL03 | 1.6mm x 2mm x 1mm |
| Wio Terminal | 72mm x 57mm x 12mm |
| Intel i9 CPU散热器和风扇 | 136mm x 145mm x 103mm |
### 框架和操作系统
由于速度和内存较低微控制器不会运行桌面意义上的操作系统OS。使你的电脑运行的操作系统Windows、Linux或macOS需要大量内存和处理能力来运行一些对微控制器完全不必要的任务。记住微控制器通常被编程为执行一个或多个非常具体的任务而不像PC或Mac这样的通用计算机需要支持用户界面、播放音乐或电影、提供文档或代码编写工具、玩游戏或浏览互联网。
要在没有操作系统的情况下编程微控制器你需要一些工具来构建代码使其能够运行在微控制器上并使用API与任何外设通信。每个微控制器都不同因此制造商通常支持标准框架允许你遵循标准“配方”来构建代码并使其能够运行在支持该框架的任何微控制器上。
你可以使用操作系统来编程微控制器——通常称为实时操作系统RTOS因为它们设计用于实时处理与外设之间的数据传输。这些操作系统非常轻量化并提供以下功能
* 多线程,允许代码同时运行多个代码块,可以在多个核心上运行,也可以在一个核心上轮流运行。
* 网络功能,支持安全的互联网通信。
* 图形用户界面GUI组件用于在带屏幕的设备上构建用户界面UI
✅ 阅读一些不同的RTOS[Azure RTOS](https://azure.microsoft.com/services/rtos/?WT.mc_id=academic-17441-jabenn)、[FreeRTOS](https://www.freertos.org)、[Zephyr](https://www.zephyrproject.org)
#### Arduino
![Arduino标志](../../../../../images/arduino-logo.svg)
[Arduino](https://www.arduino.cc)可能是最受欢迎的微控制器框架尤其是在学生、爱好者和创客中。Arduino是一个开源电子平台结合了软件和硬件。你可以从Arduino或其他制造商购买兼容的Arduino板然后使用Arduino框架进行编程。
Arduino板使用C或C++进行编程。使用C/C++可以使代码编译得非常小并运行得非常快这是微控制器这种受限设备所需要的。Arduino应用的核心被称为“草图”是包含两个函数的C/C++代码——`setup`和`loop`。当板启动时Arduino框架代码会运行一次`setup`函数,然后会不断运行`loop`函数,直到断电为止。
你会在`setup`函数中编写初始化代码例如连接WiFi和云服务或初始化输入和输出引脚。然后在`loop`函数中编写处理代码例如从传感器读取数据并将值发送到云端。通常会在每个循环中加入一个延迟例如如果你只希望每10秒发送一次传感器数据可以在循环末尾加入10秒的延迟这样微控制器可以进入休眠状态以节省电力然后在10秒后再次运行循环。
![一个Arduino草图先运行setup然后不断运行loop](../../../../../translated_images/zh-CN/arduino-sketch.79590cb837ff7a7c6a68d1afda6cab83fd53d3bb1bd9a8bf2eaf8d693a4d3ea6.png)
✅ 这种程序架构被称为*事件循环*或*消息循环*。许多应用程序在底层使用这种架构并且是大多数运行在Windows、macOS或Linux等操作系统上的桌面应用程序的标准。`loop`监听来自用户界面组件(如按钮)或设备(如键盘)的消息,并对其作出响应。你可以在这篇[关于事件循环的文章](https://wikipedia.org/wiki/Event_loop)中阅读更多内容。
Arduino提供了与微控制器和I/O引脚交互的标准库底层有不同的实现以支持不同的微控制器。例如[`delay`函数](https://www.arduino.cc/reference/en/language/functions/time/delay/)会暂停程序一段时间,[`digitalRead`函数](https://www.arduino.cc/reference/en/language/functions/digital-io/digitalread/)会从指定引脚读取`HIGH`或`LOW`值无论代码运行在哪个板上。这些标准库意味着为一个板编写的Arduino代码可以重新编译到任何其他Arduino板上并运行只要引脚相同且板支持相同的功能。
Arduino还有一个庞大的第三方库生态系统允许你为Arduino项目添加额外功能例如使用传感器和执行器或连接到云端IoT服务。
##### 任务
研究Wio Terminal。
如果你在这些课程中使用Wio Terminal请重新阅读你在上一课中编写的代码。找到`setup`和`loop`函数。监控串口输出,观察`loop`函数被反复调用。尝试在`setup`函数中添加代码以写入串口,并观察这些代码在每次重启时只被调用一次。尝试使用侧面的电源开关重启设备,观察这些代码在每次设备重启时都会被调用。
## 深入了解单板计算机
在上一课中,我们介绍了单板计算机。现在让我们深入了解它们。
### 树莓派
![树莓派标志](../../../../../translated_images/zh-CN/raspberry-pi-logo.4efaa16605cee054.webp)
[树莓派基金会](https://www.raspberrypi.org)是一个来自英国的慈善机构成立于2009年旨在促进计算机科学的学习特别是在学校层面。作为这一使命的一部分他们开发了一种单板计算机称为树莓派。目前树莓派有3种型号——全尺寸版本、更小的Pi Zero以及可以嵌入最终IoT设备的计算模块。
![树莓派4](../../../../../translated_images/zh-CN/raspberry-pi-4.fd4590d308c3d456.webp)
最新的全尺寸树莓派是树莓派4B。它拥有一个四核4核CPU运行速度为1.5GHz2GB、4GB或8GB的RAM千兆以太网WiFi2个支持4K屏幕的HDMI端口一个音频和复合视频输出端口USB端口2个USB 2.02个USB 3.040个GPIO引脚一个用于树莓派摄像头模块的摄像头连接器以及一个SD卡插槽。所有这些都集成在一个88mm x 58mm x 19.5mm的板上并由一个3A的USB-C电源供电。起售价为35美元比PC或Mac便宜得多。
> 💁 还有一个Pi400一体机带有内置键盘的Pi4。
![树莓派Zero](../../../../../translated_images/zh-CN/raspberry-pi-zero.f7a4133e1e7d54bb.webp)
Pi Zero更小功耗更低。它拥有一个单核1GHz CPU512MB的RAMWiFi在Zero W型号中一个HDMI端口一个micro-USB端口40个GPIO引脚一个用于树莓派摄像头模块的摄像头连接器以及一个SD卡插槽。它的尺寸为65mm x 30mm x 5mm功耗非常低。Zero售价为5美元带WiFi的W版本售价为10美元。
> 🎓 这两款设备的CPU都是ARM处理器而不是大多数PC和Mac中使用的Intel/AMD x86或x64处理器。这些处理器类似于一些微控制器中的CPU以及几乎所有手机、Microsoft Surface X和新的Apple Silicon Mac中使用的处理器。
所有树莓派型号都运行一个基于Debian Linux的操作系统称为树莓派OS。它有一个精简版没有桌面非常适合不需要屏幕的“无头”项目也有一个完整版带有完整的桌面环境包括网页浏览器、办公应用、编程工具和游戏。由于操作系统是Debian Linux的一个版本你可以安装任何运行在Debian上的应用或工具只要它们是为树莓派的ARM处理器构建的。
#### 任务
研究树莓派。
如果你在这些课程中使用树莓派,请阅读板上的不同硬件组件。
* 你可以在[树莓派硬件文档页面](https://www.raspberrypi.org/documentation/hardware/raspberrypi/)上找到处理器的详细信息。阅读你正在使用的树莓派的处理器信息。
* 找到GPIO引脚。在[树莓派GPIO文档](https://www.raspberrypi.org/documentation/hardware/raspberrypi/gpio/README.md)中阅读更多相关内容。使用[GPIO引脚使用指南](https://www.raspberrypi.org/documentation/usage/gpio/README.md)识别树莓派上的不同引脚。
### 编程单板计算机
单板计算机是完整的计算机运行完整的操作系统。这意味着你可以使用广泛的编程语言、框架和工具来编写代码而不像微控制器那样依赖于Arduino等框架对板的支持。大多数编程语言都有库可以访问GPIO引脚以便从传感器和执行器发送和接收数据。
✅ 你熟悉哪些编程语言它们是否支持Linux
在树莓派上构建IoT应用最常用的编程语言是Python。树莓派有一个庞大的硬件生态系统几乎所有这些硬件都包含相关的Python库代码使其能够使用。这些生态系统中的一些基于“帽子”——之所以称为“帽子”是因为它们像帽子一样安装在树莓派顶部并通过一个大插座连接到40个GPIO引脚。这些帽子提供额外的功能例如屏幕、传感器、遥控车或适配器用于连接带有标准化电缆的传感器。
### 单板计算机在专业物联网部署中的应用
单板计算机不仅仅作为开发套件,还被用于专业的物联网部署。它们可以提供强大的功能来控制硬件并运行复杂任务,例如运行机器学习模型。例如,[Raspberry Pi 4计算模块](https://www.raspberrypi.org/blog/raspberry-pi-compute-module-4/)提供了Raspberry Pi 4的全部性能但采用了更紧凑、更便宜的形式没有大多数端口专为安装到定制硬件中而设计。
---
## 🚀 挑战
上一课的挑战是列出尽可能多的你家里、学校或工作场所中的物联网设备。对于这个列表中的每个设备,你认为它们是基于微控制器、单板计算机,还是两者的混合?
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/4)
## 复习与自学
* 阅读 [Arduino入门指南](https://www.arduino.cc/en/Guide/Introduction)了解更多关于Arduino平台的信息。
* 阅读 [Raspberry Pi 4简介](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/)学习更多关于Raspberry Pi的知识。
* 在《[什么是CPU、MPU、MCU和GPU](https://www.eejournal.com/article/what-the-faq-are-cpus-mpus-mcus-and-gpus/)》这篇文章中,了解更多相关概念和缩略词。
✅ 使用这些指南,以及通过[硬件指南](../../../hardware.md)中的链接显示的成本,决定你想使用什么硬件平台,或者是否更愿意使用虚拟设备。
## 作业
[比较和对比微控制器与单板计算机](assignment.md)
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,15 @@
# 比较和对比微控制器与单板计算机
## 指导
本课程介绍了微控制器和单板计算机。创建一个表格来比较和对比它们,并列出至少两个使用微控制器而不是单板计算机的理由,以及至少两个使用单板计算机而不是微控制器的理由。
## 评分标准
| 标准 | 卓越 | 合格 | 需要改进 |
| ---- | ---- | ---- | -------- |
| 创建一个表格比较微控制器与单板计算机 | 创建了一个包含多个项目的列表,正确比较和对比 | 仅创建了一个包含少量项目的列表 | 仅能列出一个项目或没有项目进行比较和对比 |
| 使用其中一个而非另一个的理由 | 能够提供两个或更多使用微控制器的理由,以及两个或更多使用单板计算机的理由 | 仅能提供1-2个使用微控制器的理由以及1-2个使用单板计算机的理由 | 无法提供一个或多个使用微控制器或单板计算机的理由 |
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而产生的任何误解或误读不承担责任。

@ -0,0 +1,219 @@
# 使用传感器和执行器与物理世界交互
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-3.cc3b7b4cd646de598698cce043c0393fd62ef42bac2eaf60e61272cd844250f4.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看大图。
本课是 [Microsoft Reactor](https://developer.microsoft.com/reactor/?WT.mc_id=academic-17441-jabenn) 的 [Hello IoT 系列](https://youtube.com/playlist?list=PLmsFUfdnGr3xRts0TIwyaHyQuHaNQcb6-) 的一部分。课程分为两部分:一个小时的课程讲解和一个小时的答疑时间,深入探讨课程内容并解答问题。
[![课程 3使用传感器和执行器与物理世界交互](https://img.youtube.com/vi/Lqalu1v6aF4/0.jpg)](https://youtu.be/Lqalu1v6aF4)
[![课程 3使用传感器和执行器与物理世界交互 - 答疑时间](https://img.youtube.com/vi/qR3ekcMlLWA/0.jpg)](https://youtu.be/qR3ekcMlLWA)
> 🎥 点击上方图片观看视频
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/5)
## 简介
本课介绍了 IoT 设备的两个重要概念——传感器和执行器。你将亲自动手操作,向你的 IoT 项目添加一个光传感器,然后根据光线强度控制一个 LED构建一个简易的夜灯。
本课内容包括:
* [什么是传感器?](../../../../../1-getting-started/lessons/3-sensors-and-actuators)
* [如何使用传感器](../../../../../1-getting-started/lessons/3-sensors-and-actuators)
* [传感器类型](../../../../../1-getting-started/lessons/3-sensors-and-actuators)
* [什么是执行器?](../../../../../1-getting-started/lessons/3-sensors-and-actuators)
* [如何使用执行器](../../../../../1-getting-started/lessons/3-sensors-and-actuators)
* [执行器类型](../../../../../1-getting-started/lessons/3-sensors-and-actuators)
## 什么是传感器?
传感器是感知物理世界的硬件设备——它们测量周围的一个或多个属性,并将信息发送到 IoT 设备。传感器种类繁多,因为可以测量的东西非常多,从自然属性(如空气温度)到物理交互(如运动)。
一些常见的传感器包括:
* 温度传感器 - 测量空气温度或其浸入物体的温度。对于爱好者和开发者,这些传感器通常与气压和湿度传感器集成在一起。
* 按钮 - 检测是否被按下。
* 光传感器 - 检测光线强度,可以是特定颜色、紫外线、红外线或普通可见光。
* 摄像头 - 通过拍摄照片或流式视频捕捉世界的视觉表示。
* 加速度计 - 检测多方向的运动。
* 麦克风 - 检测声音,可以是一般的声音强度或方向性声音。
✅ 做一些研究。你的手机有哪些传感器?
所有传感器都有一个共同点——它们将感知到的内容转换为电信号,供 IoT 设备解读。如何解读这些电信号取决于传感器本身以及与 IoT 设备通信所使用的协议。
## 如何使用传感器
根据以下相关指南,将传感器添加到你的 IoT 设备中:
* [Arduino - Wio Terminal](wio-terminal-sensor.md)
* [单板计算机 - Raspberry Pi](pi-sensor.md)
* [单板计算机 - 虚拟设备](virtual-device-sensor.md)
## 传感器类型
传感器分为模拟传感器和数字传感器。
### 模拟传感器
一些最基本的传感器是模拟传感器。这些传感器从 IoT 设备接收电压,传感器组件调整该电压,然后测量传感器返回的电压以获得传感器值。
> 🎓 电压是衡量电流从一个地方流向另一个地方的推动力。例如,标准 AA 电池的电压为 1.5VV 是伏特的符号),可以以 1.5V 的力从正极推动电流到负极。不同的电子硬件需要不同的电压才能工作例如LED 可以在 2-3V 下点亮,而 100W 的灯泡需要 240V。你可以在 [维基百科的电压页面](https://wikipedia.org/wiki/Voltage) 上了解更多关于电压的信息。
一个例子是电位器。这是一个可以在两个位置之间旋转的旋钮,传感器测量旋转角度。
![一个电位器设置在中间位置,输入 5 伏特,输出 3.8 伏特](../../../../../translated_images/zh-CN/potentiometer.35a348b9ce22f6ec.webp)
IoT 设备会向电位器发送一个电信号,例如 5 伏特5V。当调整电位器时它会改变输出的电压。假设你有一个标有 0 到 [11](https://wikipedia.org/wiki/Up_to_eleven) 的电位器比如放大器上的音量旋钮。当电位器处于完全关闭位置0输出为 0V0 伏特。当处于完全打开位置11输出为 5V5 伏特)。
> 🎓 这是一个简化的描述,你可以在 [维基百科的电位器页面](https://wikipedia.org/wiki/Potentiometer) 上了解更多关于电位器和可变电阻的信息。
传感器输出的电压会被 IoT 设备读取,设备可以根据此值作出响应。根据传感器的不同,这个电压可以是任意值,也可以映射到标准单位。例如,基于 [热敏电阻](https://wikipedia.org/wiki/Thermistor) 的模拟温度传感器会根据温度变化其电阻。输出电压可以通过代码中的计算转换为开尔文温度,并相应地转换为摄氏度或华氏度。
✅ 如果传感器返回的电压高于输入电压(例如来自外部电源),你认为会发生什么?⛔️ 切勿尝试测试。
#### 模拟到数字转换
IoT 设备是数字化的——它们无法处理模拟值,只能处理 0 和 1。这意味着模拟传感器的值需要在处理之前转换为数字信号。许多 IoT 设备配备了模拟到数字转换器ADC用于将模拟输入转换为其数字表示。传感器也可以通过连接板与 ADC 协作。例如,在 Seeed Grove 生态系统中,使用 Raspberry Pi 时,模拟传感器连接到 Pi 上的特定端口,这些端口位于连接到 Pi GPIO 引脚的 '帽子' 上,该帽子具有 ADC可将电压转换为数字信号并通过 GPIO 引脚发送。
假设你有一个使用 3.3V 的模拟光传感器,返回值为 1V。这个 1V 在数字世界中没有意义因此需要转换。电压将根据设备和传感器的比例转换为模拟值。例如Seeed Grove 光传感器的输出值范围为 0 到 1,023。对于运行在 3.3V 的传感器1V 的输出值为 300。IoT 设备无法处理 300 作为模拟值,因此 Grove '帽子' 会将其转换为 `0000000100101100`,即 300 的二进制表示。然后 IoT 设备会处理这个值。
✅ 如果你不了解二进制,请做一些研究,学习数字如何用 0 和 1 表示。[BBC Bitesize 的二进制入门课程](https://www.bbc.co.uk/bitesize/guides/zwsbwmn/revision/1) 是一个很好的起点。
从编码的角度来看,这些通常由传感器附带的库处理,因此你不需要自己担心这些转换。例如,对于 Grove 光传感器,你可以使用 Python 库调用 `light` 属性,或者使用 Arduino 库调用 `analogRead` 来获取值 300。
### 数字传感器
数字传感器与模拟传感器一样,通过电压变化检测周围环境。不同之处在于它们输出数字信号,要么只测量两种状态,要么使用内置的 ADC。为了避免在连接板或 IoT 设备上使用 ADC数字传感器变得越来越普遍。
最简单的数字传感器是按钮或开关。这是一种只有两种状态的传感器:开或关。
![一个按钮接收 5 伏特。当未按下时返回 0 伏特,当按下时返回 5 伏特](../../../../../translated_images/zh-CN/button.eadb560b77ac45e56f523d9d8876e40444f63b419e33eb820082d461fa79490b.png)
IoT 设备上的引脚(如 GPIO 引脚)可以直接测量此信号为 0 或 1。如果返回的电压与输入电压相同则读取值为 1否则读取值为 0。无需转换信号它只能是 1 或 0。
> 💁 电压从来不是完全精确的尤其是传感器中的组件会有一些电阻因此通常会有一个容差。例如Raspberry Pi 的 GPIO 引脚工作在 3.3V 上,读取高于 1.8V 的返回信号为 1低于 1.8V 的返回信号为 0。
* 3.3V 输入按钮。按钮未按下,输出 0V值为 0。
* 3.3V 输入按钮。按钮按下,输出 3.3V,值为 1。
更高级的数字传感器会读取模拟值,然后通过内置的 ADC 转换为数字信号。例如,数字温度传感器仍然会像模拟传感器一样使用热电偶,并测量当前温度下热电偶电阻引起的电压变化。不同的是,它不会返回模拟值,而是通过内置的 ADC 转换为数字信号,并以 0 和 1 的形式发送到 IoT 设备。这些 0 和 1 的发送方式与按钮的数字信号相同1 表示全电压0 表示 0V。
![一个数字温度传感器将模拟读数转换为二进制数据0 表示 0 伏特1 表示 5 伏特,然后发送到 IoT 设备](../../../../../translated_images/zh-CN/temperature-as-digital.85004491b977bae1.webp)
发送数字数据使传感器可以变得更复杂,发送更详细的数据,甚至是加密数据以用于安全传感器。例如,摄像头是一种传感器,它捕捉图像并以包含该图像的数字数据形式发送,通常是压缩格式(如 JPEG供 IoT 设备读取。它甚至可以通过捕捉图像并逐帧发送完整图像或压缩视频流来实现视频流传输。
## 什么是执行器?
执行器与传感器相反——它们将 IoT 设备的电信号转换为与物理世界的交互,例如发光、发声或驱动电机。
一些常见的执行器包括:
* LED - 打开时发光
* 扬声器 - 根据发送的信号发出声音,从简单的蜂鸣器到可以播放音乐的音频扬声器
* 步进电机 - 将信号转换为一定量的旋转,例如旋转 90° 的旋钮
* 继电器 - 通过电信号打开或关闭的开关。它们允许 IoT 设备的小电压控制更大的电压。
* 屏幕 - 更复杂的执行器,用于在多段显示器上显示信息。屏幕从简单的 LED 显示器到高分辨率视频显示器不等。
✅ 做一些研究。你的手机有哪些执行器?
## 如何使用执行器
根据以下相关指南,将执行器添加到你的 IoT 设备中,并通过传感器控制它,构建一个 IoT 夜灯。它将从光传感器获取光线强度,并使用 LED 作为执行器,在检测到光线强度过低时发光。
![作业流程图,显示读取和检查光线强度,并控制 LED](../../../../../translated_images/zh-CN/assignment-1-flow.7552a51acb1a5ec858dca6e855cdbb44206434006df8ba3799a25afcdab1665d.png)
* [Arduino - Wio Terminal](wio-terminal-actuator.md)
* [单板计算机 - Raspberry Pi](pi-actuator.md)
* [单板计算机 - 虚拟设备](virtual-device-actuator.md)
## 执行器类型
与传感器类似,执行器分为模拟执行器和数字执行器。
### 模拟执行器
模拟执行器接收模拟信号并将其转换为某种交互,其中交互会根据提供的电压变化。
一个例子是可调光灯,例如你家中的灯。提供给灯的电压决定了它的亮度。
![低电压时灯光较暗,高电压时灯光较亮](../../../../../translated_images/zh-CN/dimmable-light.9ceffeb195dec1a849da718b2d71b32c35171ff7dfea9c07bbf82646a67acf6b.png)
与传感器类似实际的物联网设备使用的是数字信号而不是模拟信号。这意味着要发送模拟信号物联网设备需要一个数字到模拟转换器DAC可以直接集成在物联网设备上也可以在连接板上。这将把物联网设备的0和1转换为执行器可以使用的模拟电压。
✅ 如果物联网设备发送的电压高于执行器能够承受的电压,会发生什么情况?
⛔️ 请勿尝试测试。
#### 脉宽调制
将物联网设备的数字信号转换为模拟信号的另一种方法是脉宽调制。这种方法通过发送大量短数字脉冲来模拟模拟信号的效果。
例如可以使用PWM来控制电机的速度。
假设你用5V电源控制一个电机。你向电机发送一个短脉冲将电压切换到高电平5V持续0.02秒。在这段时间内电机可以旋转十分之一圈或36°。然后信号暂停0.02秒发送低电平信号0V。每个开和关的周期持续0.04秒。然后周期重复。
![电机以150 RPM旋转的脉宽调制](../../../../../translated_images/zh-CN/pwm-motor-150rpm.83347ac04ca38482.webp)
这意味着在一秒钟内你发送了25个0.02秒的5V脉冲每个脉冲使电机旋转随后是0.02秒的0V暂停电机不旋转。每个脉冲使电机旋转十分之一圈这意味着电机每秒完成2.5圈。你使用数字信号使电机以每秒2.5圈或150 [每分钟转速](https://wikipedia.org/wiki/Revolutions_per_minute)(一种非标准的旋转速度测量单位)旋转。
```output
25 pulses per second x 0.1 rotations per pulse = 2.5 rotations per second
2.5 rotations per second x 60 seconds in a minute = 150rpm
```
> 🎓 当PWM信号开启时间和关闭时间各占一半时这被称为[50%占空比](https://wikipedia.org/wiki/Duty_cycle)。占空比是信号处于开启状态的时间与关闭状态时间的百分比。
![电机以75 RPM旋转的脉宽调制](../../../../../translated_images/zh-CN/pwm-motor-75rpm.a5e4c939934b6e14.webp)
可以通过改变脉冲的大小来调整电机速度。例如对于同一个电机可以保持周期时间为0.04秒将开启脉冲减半为0.01秒关闭脉冲增加到0.03秒。每秒的脉冲数量25个保持不变但每个开启脉冲的长度减半。一个半长度的脉冲只能使电机旋转二十分之一圈而每秒25个脉冲将使电机完成1.25圈或75rpm。通过改变数字信号的脉冲速度你将模拟电机的速度减半。
```output
25 pulses per second x 0.05 rotations per pulse = 1.25 rotations per second
1.25 rotations per second x 60 seconds in a minute = 75rpm
```
✅ 如何保持电机在低速时的平稳旋转?你会选择少量长脉冲和长暂停,还是大量非常短的脉冲和非常短的暂停?
> 💁 一些传感器也使用PWM将模拟信号转换为数字信号。
> 🎓 你可以在[维基百科的脉宽调制页面](https://wikipedia.org/wiki/Pulse-width_modulation)上阅读更多关于脉宽调制的信息。
### 数字执行器
数字执行器与数字传感器类似要么通过高电压或低电压控制两种状态要么内置DAC可以将数字信号转换为模拟信号。
一个简单的数字执行器是LED。当设备发送数字信号1时会发送高电压点亮LED。当发送数字信号0时电压降至0VLED熄灭。
![LED在0伏时熄灭在5伏时点亮](../../../../../translated_images/zh-CN/led.ec6d94f66676a174ad06d9fa9ea49c2ee89beb18b312d5c6476467c66375b07f.png)
✅ 你能想到其他简单的两状态执行器吗?一个例子是电磁铁,它可以被激活来完成诸如移动门栓锁定/解锁门的任务。
更高级的数字执行器,例如屏幕,需要以特定格式发送数字数据。它们通常附带库,使发送正确数据以控制它们变得更容易。
---
## 🚀 挑战
上一课的挑战是列出你家里、学校或工作场所中的尽可能多的物联网设备,并判断它们是基于微控制器还是单板计算机,或者两者的混合。
对于你列出的每个设备,它们连接了哪些传感器和执行器?这些设备连接的每个传感器和执行器的用途是什么?
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/6)
## 复习与自学
* 在 [ThingLearn](http://thinglearn.jenlooper.com/curriculum/) 上阅读关于电力和电路的内容。
* 在 [Seeed Studios 温度传感器指南](https://www.seeedstudio.com/blog/2019/10/14/temperature-sensors-for-arduino-projects/) 上阅读不同类型的温度传感器。
* 在 [维基百科 LED 页面](https://wikipedia.org/wiki/Light-emitting_diode) 上阅读关于LED的内容。
## 作业
[研究传感器和执行器](assignment.md)
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,20 @@
# 研究传感器和执行器
## 指导说明
本课介绍了传感器和执行器。请研究并描述一个可以与物联网开发套件配合使用的传感器和一个执行器,包括以下内容:
* 它的功能
* 内部使用的电子元件/硬件
* 它是模拟的还是数字的
* 输入或测量的单位和范围
## 评分标准
| 标准 | 优秀 | 合格 | 需要改进 |
| -------- | --------- | -------- | ----------------- |
| 描述一个传感器 | 描述了一个传感器并详细涵盖了上述4个部分。 | 描述了一个传感器但仅能提供上述2-3个部分的信息。 | 描述了一个传感器但仅能提供上述1个部分的信息。 |
| 描述一个执行器 | 描述了一个执行器并详细涵盖了上述4个部分。 | 描述了一个执行器但仅能提供上述2-3个部分的信息。 | 描述了一个执行器但仅能提供上述1个部分的信息。 |
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,119 @@
# 制作一个夜灯 - 树莓派
在本课的这一部分你将为树莓派添加一个LED并用它来制作一个夜灯。
## 硬件
夜灯现在需要一个执行器。
执行器是一个**LED**,即[发光二极管](https://wikipedia.org/wiki/Light-emitting_diode)当电流通过时会发光。这是一种数字执行器具有两种状态开和关。发送值1会点亮LED发送值0会关闭LED。LED是一个外部的Grove执行器需要连接到树莓派上的Grove Base帽。
夜灯的逻辑伪代码如下:
```output
Check the light level.
If the light is less than 300
Turn the LED on
Otherwise
Turn the LED off
```
### 连接LED
Grove LED是一个模块提供多种颜色的LED供选择。
#### 任务 - 连接LED
连接LED。
![一个Grove LED](../../../../../translated_images/zh-CN/grove-led.6c853be93f473cf2c439cfc74bb1064732b22251a83cedf66e62f783f9cc1a79.png)
1. 选择你喜欢的LED将其引脚插入LED模块上的两个孔中。
LED是发光二极管而二极管是一种只能单向导电的电子器件。这意味着LED必须以正确的方向连接否则无法工作。
LED的一个引脚是正极另一个是负极。LED并不是完全圆形的一端会稍微平一些。稍微平的一侧是负极。当你将LED连接到模块时请确保圆形一侧的引脚连接到模块外侧标有**+**的插座,而平的一侧连接到模块中间较近的插座。
1. LED模块有一个旋钮可以控制亮度。首先用小号十字螺丝刀将其逆时针旋转到最大亮度。
1. 将Grove电缆的一端插入LED模块上的插座。电缆只能以一种方向插入。
1. 在树莓派断电的情况下将Grove电缆的另一端连接到树莓派上Grove Base帽的数字插座**D5**。这个插座位于GPIO引脚旁边的一排插座中从左数第二个。
![连接到D5插座的Grove LED](../../../../../translated_images/zh-CN/pi-led.97f1d474981dc35d1c7996c7b17de355d3d0a6bc9606d79fa5f89df933415122.png)
## 编程夜灯
现在可以使用Grove光传感器和Grove LED来编程夜灯。
### 任务 - 编程夜灯
编程夜灯。
1. 启动树莓派并等待其启动。
1. 在VS Code中打开你在本作业前一部分中创建的夜灯项目可以直接在树莓派上运行也可以通过Remote SSH扩展连接。
1. 在`app.py`文件顶部的其他`import`行下面添加以下代码以导入所需的库:
```python
from grove.grove_led import GroveLed
```
`from grove.grove_led import GroveLed`语句从Grove Python库中导入`GroveLed`。这个库包含与Grove LED交互的代码。
1. 在`light_sensor`声明之后添加以下代码以创建管理LED的类实例
```python
led = GroveLed(5)
```
`led = GroveLed(5)`这行代码创建了一个连接到引脚**D5**的`GroveLed`类实例——LED连接的数字Grove引脚。
> 💁 每个插座都有唯一的引脚编号。引脚0、2、4和6是模拟引脚引脚5、16、18、22、24和26是数字引脚。
1. 在`while`循环内添加一个检查,在`time.sleep`之前检查光线水平并打开或关闭LED
```python
if light < 300:
led.on()
else:
led.off()
```
这段代码检查`light`值。如果值小于300则调用`GroveLed`类的`on`方法向LED发送数字值1点亮LED。如果光线值大于或等于300则调用`off`方法发送数字值0关闭LED。
> 💁 这段代码应该与`print('Light level:', light)`行保持相同的缩进级别,以确保它在`while`循环内!
> 💁 向执行器发送数字值时值0表示0V值1表示设备的最大电压。对于树莓派使用的Grove传感器和执行器值1的电压为3.3V。
1. 在VS Code终端中运行以下命令以运行你的Python应用程序
```sh
python3 app.py
```
光线值将输出到控制台。
```output
pi@raspberrypi:~/nightlight $ python3 app.py
Light level: 634
Light level: 634
Light level: 634
Light level: 230
Light level: 104
Light level: 290
```
1. 遮挡和移开光传感器。注意当光线水平为300或以下时LED会点亮当光线水平大于300时LED会熄灭。
> 💁 如果LED没有点亮请确保它连接的方向正确并且旋钮已调到最大亮度。
![LED连接到树莓派并随着光线水平变化而亮灭](../../../../../images/pi-running-assignment-1-1.gif)
> 💁 你可以在[code-actuator/pi](../../../../../1-getting-started/lessons/3-sensors-and-actuators/code-actuator/pi)文件夹中找到这段代码。
😀 你的夜灯程序成功了!
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,99 @@
# 制作一个夜灯 - Raspberry Pi
在本课的这一部分,你将为你的 Raspberry Pi 添加一个光传感器。
## 硬件
本课使用的传感器是一个**光传感器**,它通过[光电二极管](https://wikipedia.org/wiki/Photodiode)将光转换为电信号。这是一个模拟传感器,会发送一个从 0 到 1,000 的整数值,表示相对光量,但不对应任何标准测量单位,例如[lux](https://wikipedia.org/wiki/Lux)。
光传感器是一个外部 Grove 传感器,需要连接到 Raspberry Pi 上的 Grove Base hat。
### 连接光传感器
用于检测光线强度的 Grove 光传感器需要连接到 Raspberry Pi。
#### 任务 - 连接光传感器
连接光传感器
![一个 Grove 光传感器](../../../../../translated_images/zh-CN/grove-light-sensor.b8127b7c434e632d6bcdb57587a14e9ef69a268a22df95d08628f62b8fa5505c.png)
1. 将 Grove 电缆的一端插入光传感器模块上的插座。电缆只能以一种方向插入。
1. 在 Raspberry Pi 断电的情况下,将 Grove 电缆的另一端连接到 Grove Base hat 上标记为 **A0** 的模拟插座。该插座位于 GPIO 引脚旁边插座排的第二个位置。
![连接到 A0 插座的 Grove 光传感器](../../../../../translated_images/zh-CN/pi-light-sensor.66cc1e31fa48cd7d5f23400d4b2119aa41508275cb7c778053a7923b4e972d7e.png)
## 编程光传感器
现在可以使用 Grove 光传感器对设备进行编程。
### 任务 - 编程光传感器
编程设备。
1. 启动 Raspberry Pi 并等待其完成启动。
1. 在 VS Code 中打开你在本任务前一部分创建的夜灯项目,可以直接在 Pi 上运行,也可以通过 Remote SSH 扩展连接。
1. 打开 `app.py` 文件,并删除其中的所有代码。
1. 在 `app.py` 文件中添加以下代码以导入一些所需的库:
```python
import time
from grove.grove_light_sensor_v1_2 import GroveLightSensor
```
`import time` 语句导入了 `time` 模块,该模块将在本任务后续部分使用。
`from grove.grove_light_sensor_v1_2 import GroveLightSensor` 语句从 Grove Python 库中导入了 `GroveLightSensor`。该库包含与 Grove 光传感器交互的代码,并在设置 Pi 时已全局安装。
1. 在上述代码之后添加以下代码,以创建一个管理光传感器的类实例:
```python
light_sensor = GroveLightSensor(0)
```
`light_sensor = GroveLightSensor(0)` 这一行代码创建了一个连接到 **A0** 引脚的 `GroveLightSensor` 类实例——光传感器连接的 Grove 模拟引脚。
1. 在上述代码之后添加一个无限循环,用于轮询光传感器的值并将其打印到控制台:
```python
while True:
light = light_sensor.light
print('Light level:', light)
```
这段代码将使用 `GroveLightSensor` 类的 `light` 属性读取当前光线强度,范围为 0-1,023。该属性从引脚读取模拟值然后将该值打印到控制台。
1. 在循环的末尾添加一个一秒的短暂休眠,因为不需要连续检查光线强度。休眠可以降低设备的功耗。
```python
time.sleep(1)
```
1. 在 VS Code 的终端中运行以下命令以运行你的 Python 应用程序:
```sh
python3 app.py
```
光线值将输出到控制台。遮挡或移开光传感器,值会发生变化:
```output
pi@raspberrypi:~/nightlight $ python3 app.py
Light level: 634
Light level: 634
Light level: 634
Light level: 230
Light level: 104
Light level: 290
```
> 💁 你可以在 [code-sensor/pi](../../../../../1-getting-started/lessons/3-sensors-and-actuators/code-sensor/pi) 文件夹中找到这段代码。
😀 为你的夜灯程序添加传感器成功了!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们尽力确保准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。对于因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,113 @@
# 制作一个夜灯 - 虚拟物联网硬件
在本节课程中您将为虚拟物联网设备添加一个LED并用它来创建一个夜灯。
## 虚拟硬件
夜灯需要一个执行器,在 CounterFit 应用中创建。
这个执行器是一个 **LED**。在物理物联网设备中,它是一个[发光二极管](https://wikipedia.org/wiki/Light-emitting_diode)当电流通过时会发光。这是一个数字执行器具有两种状态开和关。发送值1会点亮LED发送值0会关闭它。
夜灯逻辑的伪代码如下:
```output
Check the light level.
If the light is less than 300
Turn the LED on
Otherwise
Turn the LED off
```
### 在 CounterFit 中添加执行器
要使用虚拟LED您需要将其添加到 CounterFit 应用中。
#### 任务 - 在 CounterFit 中添加执行器
将LED添加到 CounterFit 应用中。
1. 确保 CounterFit 网页应用正在运行(从本作业的上一部分开始)。如果没有运行,请启动它并重新添加光传感器。
1. 创建一个LED
1. 在 *Actuator* 面板的 *Create actuator* 框中,点击 *Actuator type* 下拉框并选择 *LED*
1. 将 *Pin* 设置为 *5*
1. 点击 **Add** 按钮在引脚5上创建LED。
![LED设置](../../../../../translated_images/zh-CN/counterfit-create-led.ba9db1c9b8c622a635d6dfae5cdc4e70c2b250635bd4f0601c6cf0bd22b7ba46.png)
LED将被创建并显示在执行器列表中。
![创建的LED](../../../../../translated_images/zh-CN/counterfit-led.c0ab02de6d256ad84d9bad4d67a7faa709f0ea83e410cfe9b5561ef0cef30b1c.png)
创建LED后您可以使用 *Color* 选择器更改颜色。选择颜色后,点击 **Set** 按钮以更改颜色。
### 编程夜灯
现在可以使用 CounterFit 光传感器和LED来编程夜灯。
#### 任务 - 编程夜灯
编程夜灯。
1. 打开您在本作业上一部分中创建的夜灯项目VS Code。如果需要终止并重新创建终端以确保它使用虚拟环境运行。
1. 打开 `app.py` 文件。
1. 在 `app.py` 文件顶部的其他 `import` 行下方,添加以下代码以导入所需的库:
```python
from counterfit_shims_grove.grove_led import GroveLed
```
`from counterfit_shims_grove.grove_led import GroveLed` 语句从 CounterFit Grove shim Python 库中导入 `GroveLed`。该库包含与 CounterFit 应用中创建的LED交互的代码。
1. 在 `light_sensor` 声明之后添加以下代码以创建管理LED的类实例
```python
led = GroveLed(5)
```
这行代码 `led = GroveLed(5)` 创建了一个 `GroveLed` 类的实例,连接到引脚 **5**,即 CounterFit Grove 中连接LED的引脚。
1. 在 `while` 循环内、`time.sleep` 之前添加一个检查用于检测光线水平并打开或关闭LED
```python
if light < 300:
led.on()
else:
led.off()
```
这段代码检查 `light` 值。如果小于300则调用 `GroveLed` 类的 `on` 方法向LED发送数字值1点亮LED。如果光线值大于或等于300则调用 `off` 方法发送数字值0关闭LED。
> 💁 这段代码应与 `print('Light level:', light)` 行保持相同的缩进级别,以确保它位于 while 循环内!
1. 在 VS Code 的终端中运行以下命令以运行您的 Python 应用:
```sh
python3 app.py
```
光线值将输出到控制台。
```output
(.venv) ➜ GroveTest python3 app.py
Light level: 143
Light level: 244
Light level: 246
Light level: 253
```
1. 更改 *Value**Random* 设置使光线水平在300以上和以下变化。LED将会亮起和熄灭。
![CounterFit 应用中LED随着光线水平变化而亮灭](../../../../../images/virtual-device-running-assignment-1-1.gif)
> 💁 您可以在 [code-actuator/virtual-device](../../../../../1-getting-started/lessons/3-sensors-and-actuators/code-actuator/virtual-device) 文件夹中找到此代码。
😀 您的夜灯程序成功了!
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,113 @@
# 制作一个夜灯 - 虚拟物联网硬件
在本节课程中,您将为虚拟物联网设备添加一个光传感器。
## 虚拟硬件
夜灯需要一个传感器,通过 CounterFit 应用程序创建。
这个传感器是一个**光传感器**。在实际的物联网设备中,它会是一个[光电二极管](https://wikipedia.org/wiki/Photodiode),将光转换为电信号。光传感器是一种模拟传感器,它发送一个整数值,表示相对的光量,但这个值并不对应任何标准的测量单位,例如 [lux](https://wikipedia.org/wiki/Lux)。
### 将传感器添加到 CounterFit
要使用虚拟光传感器,您需要将其添加到 CounterFit 应用程序中。
#### 任务 - 将传感器添加到 CounterFit
将光传感器添加到 CounterFit 应用程序中。
1. 确保 CounterFit Web 应用程序正在运行(从本作业的上一部分开始)。如果没有,请启动它。
1. 创建一个光传感器:
1. 在 *Sensors* 面板的 *Create sensor* 框中,点击 *Sensor type* 下拉框并选择 *Light*
1. 将 *Units* 保持为 *NoUnits*
1. 确保 *Pin* 设置为 *0*
1. 点击 **Add** 按钮,在 Pin 0 上创建光传感器。
![光传感器设置](../../../../../translated_images/zh-CN/counterfit-create-light-sensor.9f36a5e0d4458d8d554d54b34d2c806d56093d6e49fddcda2d20f6fef7f5cce1.png)
光传感器将被创建并显示在传感器列表中。
![光传感器已创建](../../../../../translated_images/zh-CN/counterfit-light-sensor.5d0f5584df56b90f6b2561910d9cb20dfbd73eeff2177c238d38f4de54aefae1.png)
## 编程光传感器
现在可以对设备进行编程,以使用内置的光传感器。
### 任务 - 编程光传感器
对设备进行编程。
1. 在 VS Code 中打开您在本作业上一部分中创建的夜灯项目。如果需要,请终止并重新创建终端,以确保它正在使用虚拟环境运行。
1. 打开 `app.py` 文件。
1. 在 `app.py` 文件顶部的 `import` 语句部分添加以下代码,以导入一些所需的库:
```python
import time
from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor
```
`import time` 语句导入了 Python 的 `time` 模块,该模块将在本作业后续部分使用。
`from counterfit_shims_grove.grove_light_sensor_v1_2 import GroveLightSensor` 语句从 CounterFit Grove shim Python 库中导入了 `GroveLightSensor`。该库包含与 CounterFit 应用程序中创建的光传感器交互的代码。
1. 在文件底部添加以下代码,以创建管理光传感器的类实例:
```python
light_sensor = GroveLightSensor(0)
```
`light_sensor = GroveLightSensor(0)` 这一行创建了一个连接到 Pin **0**`GroveLightSensor` 类实例——这是光传感器连接到的 CounterFit Grove 引脚。
1. 在上述代码之后添加一个无限循环,用于轮询光传感器的值并将其打印到控制台:
```python
while True:
light = light_sensor.light
print('Light level:', light)
```
这段代码将使用 `GroveLightSensor` 类的 `light` 属性读取当前光照水平。该属性从引脚读取模拟值,然后将该值打印到控制台。
1. 在 `while` 循环的末尾添加一个一秒的延迟,因为光照水平不需要被连续检查。延迟可以减少设备的功耗。
```python
time.sleep(1)
```
1. 在 VS Code 的终端中运行以下命令以运行您的 Python 应用程序:
```sh
python3 app.py
```
光照值将输出到控制台。初始值将为 0。
1. 在 CounterFit 应用程序中更改光传感器的值,该值将被应用程序读取。您可以通过以下两种方式更改:
* 在光传感器的 *Value* 框中输入一个数字,然后点击 **Set** 按钮。您输入的数字将是传感器返回的值。
* 勾选 *Random* 复选框,并输入 *Min**Max* 值,然后点击 **Set** 按钮。每次传感器读取值时,它将读取一个介于 *Min**Max* 之间的随机数。
您设置的值将输出到控制台。更改 *Value**Random* 设置以使值发生变化。
```output
(.venv) ➜ GroveTest python3 app.py
Light level: 143
Light level: 244
Light level: 246
Light level: 253
```
> 💁 您可以在 [code-sensor/virtual-device](../../../../../1-getting-started/lessons/3-sensors-and-actuators/code-sensor/virtual-device) 文件夹中找到这段代码。
😀 您的夜灯程序成功了!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。对于因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,113 @@
# 制作一个夜灯 - Wio Terminal
在本节课程中,您将为 Wio Terminal 添加一个 LED并用它来制作一个夜灯。
## 硬件
现在夜灯需要一个执行器。
执行器是一个 **LED**,即[发光二极管](https://wikipedia.org/wiki/Light-emitting_diode),当电流通过时会发光。这是一个数字执行器,只有两种状态:开和关。发送值 1 会点亮 LED发送值 0 会关闭 LED。这是一个外部的 Grove 执行器,需要连接到 Wio Terminal。
夜灯的逻辑伪代码如下:
```output
Check the light level.
If the light is less than 300
Turn the LED on
Otherwise
Turn the LED off
```
### 连接 LED
Grove LED 是一个模块,包含多种颜色的 LED您可以选择自己喜欢的颜色。
#### 任务 - 连接 LED
连接 LED。
![一个 Grove LED](../../../../../translated_images/zh-CN/grove-led.6c853be93f473cf2c439cfc74bb1064732b22251a83cedf66e62f783f9cc1a79.png)
1. 选择您喜欢的 LED将其引脚插入 LED 模块上的两个孔中。
LED 是发光二极管,而二极管是一种只能单向导电的电子器件。这意味着 LED 必须以正确的方向连接,否则无法工作。
LED 的一个引脚是正极另一个是负极。LED 并不是完全圆形的,其中一侧稍微平一些。稍微平一些的一侧是负极。当您将 LED 连接到模块时,请确保圆润一侧的引脚连接到模块外侧标有 **+** 的插座,而平坦一侧的引脚连接到模块中间较近的插座。
1. LED 模块上有一个旋钮,可以用来调节亮度。使用小号十字螺丝刀将其逆时针旋转到最亮的位置。
1. 将 Grove 电缆的一端插入 LED 模块上的插座。电缆只能以一种方向插入。
1. 在 Wio Terminal 未连接到电脑或其他电源的情况下,将 Grove 电缆的另一端连接到 Wio Terminal 屏幕右侧的 Grove 插座。这是离电源按钮最远的插座。
> 💁 右侧的 Grove 插座可以用于模拟或数字传感器和执行器。左侧插座仅用于 I2C 和数字传感器及执行器。
![Grove LED 连接到右侧插座](../../../../../translated_images/zh-CN/wio-led.265a1897e72d7f21.webp)
## 编程夜灯
现在可以使用内置的光传感器和 Grove LED 来编程夜灯。
### 任务 - 编程夜灯
编程夜灯。
1. 在 VS Code 中打开您在本作业前一部分中创建的夜灯项目。
1. 在 `setup` 函数的底部添加以下代码行:
```cpp
pinMode(D0, OUTPUT);
```
这行代码配置了用于通过 Grove 端口与 LED 通信的引脚。
`D0` 引脚是右侧 Grove 插座的数字引脚。该引脚被设置为 `OUTPUT`,表示它连接到一个执行器,并将数据写入该引脚。
1. 在 `loop` 函数中的 `delay` 之前立即添加以下代码:
```cpp
if (light < 300)
{
digitalWrite(D0, HIGH);
}
else
{
digitalWrite(D0, LOW);
}
```
这段代码检查 `light` 值。如果该值小于 300则向 `D0` 数字引脚发送 `HIGH` 值。`HIGH` 值为 1会点亮 LED。如果光线值大于或等于 300则向引脚发送 `LOW` 值,关闭 LED。
> 💁 当向执行器发送数字值时,`LOW` 值为 0V`HIGH` 值为设备的最大电压。对于 Wio Terminal`HIGH` 电压为 3.3V。
1. 重新将 Wio Terminal 连接到您的电脑,并像之前一样上传新代码。
1. 连接串口监视器。光线值将输出到终端。
```output
> Executing task: platformio device monitor <
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at http://bit.ly/pio-monitor-filters
--- Miniterm on /dev/cu.usbmodem101 9600,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
Light value: 4
Light value: 5
Light value: 4
Light value: 158
Light value: 343
Light value: 348
Light value: 344
```
1. 遮住和移开光传感器。注意,当光线值小于或等于 300 时LED 会点亮;当光线值大于 300 时LED 会熄灭。
![LED 连接到 Wio根据光线变化亮灭](../../../../../images/wio-running-assignment-1-1.gif)
> 💁 您可以在 [code-actuator/wio-terminal](../../../../../1-getting-started/lessons/3-sensors-and-actuators/code-actuator/wio-terminal) 文件夹中找到这段代码。
😀 您的夜灯程序成功了!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,76 @@
# 添加传感器 - Wio Terminal
在本节课程中,您将使用 Wio Terminal 上的光传感器。
## 硬件
本节课程使用的传感器是一个**光传感器**,它通过[光电二极管](https://wikipedia.org/wiki/Photodiode)将光转换为电信号。这是一个模拟传感器,会发送一个从 0 到 1,023 的整数值,表示相对光量,但不对应任何标准测量单位,例如 [lux](https://wikipedia.org/wiki/Lux)。
光传感器内置在 Wio Terminal 中,可以通过背面的透明塑料窗口看到。
![Wio Terminal 背面的光传感器](../../../../../translated_images/zh-CN/wio-light-sensor.b1f529f3c95f5165.webp)
## 编程光传感器
现在可以对设备进行编程以使用内置的光传感器。
### 任务
对设备进行编程。
1. 在 VS Code 中打开您在上一部分任务中创建的夜灯项目。
1. 在 `setup` 函数的底部添加以下代码行:
```cpp
pinMode(WIO_LIGHT, INPUT);
```
这行代码配置了用于与传感器硬件通信的引脚。
`WIO_LIGHT` 引脚是连接到板载光传感器的 GPIO 引脚编号。该引脚被设置为 `INPUT`,表示它连接到传感器并将从引脚读取数据。
1. 删除 `loop` 函数的内容。
1. 在现在空的 `loop` 函数中添加以下代码:
```cpp
int light = analogRead(WIO_LIGHT);
Serial.print("Light value: ");
Serial.println(light);
```
这段代码从 `WIO_LIGHT` 引脚读取一个模拟值。它从板载光传感器读取一个 0-1,023 的值。然后,这个值会被发送到串口,您可以在代码运行时通过串行监视器读取它。`Serial.print` 会写入文本但不带换行符,因此每行都会以 `Light value:` 开头,并以实际的光值结尾。
1. 在 `loop` 的末尾添加一个一秒1,000 毫秒)的短暂延迟,因为光照水平不需要连续检查。延迟可以降低设备的功耗。
```cpp
delay(1000);
```
1. 将 Wio Terminal 重新连接到您的电脑,并像之前一样上传新代码。
1. 连接串行监视器。光值会输出到终端。遮盖或移开 Wio Terminal 背面的光传感器,数值会发生变化。
```output
> Executing task: platformio device monitor <
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at http://bit.ly/pio-monitor-filters
--- Miniterm on /dev/cu.usbmodem101 9600,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
Light value: 4
Light value: 5
Light value: 4
Light value: 158
Light value: 343
Light value: 348
Light value: 344
```
> 💁 您可以在 [code-sensor/wio-terminal](../../../../../1-getting-started/lessons/3-sensors-and-actuators/code-sensor/wio-terminal) 文件夹中找到这段代码。
😀 成功为您的夜灯程序添加了一个传感器!
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,455 @@
# 将设备连接到互联网
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-4.7344e074ea68fa545fd320b12dce36d72dd62d28c3b4596cb26cf315f434b98f.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看更大版本。
本课是 [Microsoft Reactor](https://developer.microsoft.com/reactor/?WT.mc_id=academic-17441-jabenn) 的 [Hello IoT 系列](https://youtube.com/playlist?list=PLmsFUfdnGr3xRts0TIwyaHyQuHaNQcb6-) 的一部分。课程分为两部分:一个小时的课程视频,以及一个小时的答疑视频,深入探讨课程内容并回答问题。
[![课程 4将设备连接到互联网](https://img.youtube.com/vi/O4dd172mZhs/0.jpg)](https://youtu.be/O4dd172mZhs)
[![课程 4将设备连接到互联网 - 答疑时间](https://img.youtube.com/vi/j-cVCzRDE2Q/0.jpg)](https://youtu.be/j-cVCzRDE2Q)
> 🎥 点击上方图片观看视频
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/7)
## 简介
IoT 中的 **I** 代表 **Internet**(互联网)——云连接和服务使 IoT 设备的许多功能得以实现例如从连接到设备的传感器收集数据或发送消息控制执行器。IoT 设备通常使用标准通信协议连接到单一的云 IoT 服务,而该服务则连接到 IoT 应用的其他部分,例如 AI 服务用于智能决策,或用于控制和报告的 Web 应用。
> 🎓 从传感器收集并发送到云的数据称为遥测数据。
IoT 设备可以接收来自云的消息。这些消息通常包含命令——即指示设备执行某些操作的指令,可以是内部操作(如重启或更新固件),也可以是使用执行器(如打开灯)。
本课将介绍 IoT 设备连接到云时可以使用的一些通信协议,以及它们可能发送或接收的数据类型。您还将亲自动手实践,将互联网控制功能添加到您的夜灯中,并将 LED 控制逻辑移至本地运行的“服务器”代码。
在本课中,我们将学习:
* [通信协议](../../../../../1-getting-started/lessons/4-connect-internet)
* [消息队列遥测传输 (MQTT)](../../../../../1-getting-started/lessons/4-connect-internet)
* [遥测数据](../../../../../1-getting-started/lessons/4-connect-internet)
* [命令](../../../../../1-getting-started/lessons/4-connect-internet)
## 通信协议
IoT 设备与互联网通信时有许多流行的通信协议。最流行的协议基于通过某种代理进行发布/订阅消息传递。IoT 设备连接到代理并发布遥测数据,同时订阅命令。云服务也连接到代理,订阅所有遥测消息,并发布命令给特定设备或设备组。
![IoT 设备连接到代理并发布遥测数据,同时订阅命令。云服务连接到代理,订阅所有遥测数据,并向特定设备发送命令。](../../../../../translated_images/zh-CN/pub-sub.7c7ed43fe9fd15d4.webp)
MQTT 是 IoT 设备最流行的通信协议之一,本课将重点介绍它。其他协议包括 AMQP 和 HTTP/HTTPS。
## 消息队列遥测传输 (MQTT)
[MQTT](http://mqtt.org) 是一种轻量级、开放标准的消息传递协议,可在设备之间发送消息。它最初于 1999 年设计用于监控石油管道15 年后由 IBM 发布为开放标准。
MQTT 有一个单一的代理和多个客户端。所有客户端都连接到代理,代理根据需要将消息路由到相关客户端。消息通过命名主题进行路由,而不是直接发送到单个客户端。客户端可以发布到某个主题,订阅该主题的任何客户端都会收到消息。
![IoT 设备在 /telemetry 主题上发布遥测数据,云服务订阅该主题](../../../../../translated_images/zh-CN/mqtt.cbf7f21d9adc3e17548b359444cc11bb4bf2010543e32ece9a47becf54438c23.png)
✅ 做一些研究。如果您有大量 IoT 设备,如何确保您的 MQTT 代理能够处理所有消息?
### 将 IoT 设备连接到 MQTT
为夜灯添加互联网控制的第一步是将其连接到 MQTT 代理。
#### 任务
将设备连接到 MQTT 代理。
在本课的这一部分,您将把 IoT 夜灯连接到互联网,以便远程控制它。在本课的后续部分,您的 IoT 设备将通过 MQTT 将光线强度的遥测消息发送到公共 MQTT 代理,您将编写一些服务器代码来接收这些消息。该代码将检查光线强度,并发送命令消息回设备,指示它打开或关闭 LED。
这种设置的实际应用场景可能是从多个光传感器收集数据后再决定是否打开灯光,例如在有大量灯光的场所(如体育场)。这样可以避免仅因一个传感器被云层或鸟遮挡而打开灯光,而其他传感器检测到足够的光线。
✅ 还有哪些情况需要评估多个传感器的数据后再发送命令?
为了简化本次作业中设置 MQTT 代理的复杂性,您可以使用运行 [Eclipse Mosquitto](https://www.mosquitto.org) 的公共测试服务器,这是一个开源的 MQTT 代理。此测试代理可通过 [test.mosquitto.org](https://test.mosquitto.org) 公开访问,无需设置账户,是测试 MQTT 客户端和服务器的绝佳工具。
> 💁 此测试代理是公开的且不安全。任何人都可以监听您发布的内容,因此不应用于需要保密的数据。
![作业流程图显示光线强度被读取和检查LED 被控制](../../../../../translated_images/zh-CN/assignment-1-internet-flow.3256feab5f052fd273bf4e331157c574c2c3fa42e479836fc9c3586f41db35a5.png)
按照以下相关步骤将设备连接到 MQTT 代理:
* [Arduino - Wio Terminal](wio-terminal-mqtt.md)
* [单板计算机 - Raspberry Pi/虚拟 IoT 设备](single-board-computer-mqtt.md)
### 深入了解 MQTT
主题可以有层级结构,客户端可以使用通配符订阅不同层级。例如,您可以将温度遥测消息发送到 `/telemetry/temperature` 主题,将湿度消息发送到 `/telemetry/humidity` 主题,然后在云应用中订阅 `/telemetry/*` 主题以接收温度和湿度的遥测消息。
消息可以设置服务质量 (QoS),以确定消息接收的保证级别。
* 最多一次 - 消息仅发送一次,客户端和代理不采取额外步骤确认交付(即发出后不管)。
* 至少一次 - 发送方会多次重试发送消息,直到收到确认(即确认交付)。
* 仅一次 - 发送方和接收方进行两级握手,以确保消息仅接收一次(即保证交付)。
✅ 哪些情况下需要保证交付的消息,而不是发出后不管的消息?
尽管 MQTT 的名称中有“消息队列”Message Queueing但它实际上并不支持消息队列。这意味着如果客户端断开连接然后重新连接它不会接收到断开期间发送的消息除非这些消息已经通过 QoS 过程开始处理。消息可以设置保留标志。如果设置了该标志MQTT 代理会存储该主题上最后发送的消息,并将其发送给后来订阅该主题的客户端。这样,客户端总能获得最新消息。
MQTT 还支持保持连接功能,在消息间隔较长时检查连接是否仍然有效。
> 🦟 [Eclipse Foundation 的 Mosquitto](https://mosquitto.org) 提供了一个免费的 MQTT 代理,您可以自己运行以实验 MQTT还提供了一个公共 MQTT 代理供您测试代码,地址为 [test.mosquitto.org](https://test.mosquitto.org)。
MQTT 连接可以是公开和开放的,也可以通过用户名和密码或证书进行加密和安全保护。
> 💁 MQTT 通过 TCP/IP 通信,与 HTTP 使用相同的底层网络协议,但端口不同。您还可以通过 WebSocket 使用 MQTT与运行在浏览器中的 Web 应用通信,或在防火墙或其他网络规则阻止标准 MQTT 连接的情况下使用。
## 遥测数据
“遥测”一词源于希腊语,意为远程测量。遥测是从传感器收集数据并将其发送到云的过程。
> 💁 最早的遥测设备之一于 1874 年在法国发明,它通过物理电线将实时天气和雪深数据从勃朗峰发送到巴黎,当时尚未有无线技术。
让我们回顾一下第 1 课中的智能恒温器示例。
![一个使用多个房间传感器的互联网连接恒温器](../../../../../translated_images/zh-CN/telemetry.21e5d8b97649d2eb.webp)
恒温器有温度传感器用于收集遥测数据。它可能内置一个温度传感器,并可能通过无线协议(如 [蓝牙低功耗](https://wikipedia.org/wiki/Bluetooth_Low_Energy))连接到多个外部温度传感器。
它发送的遥测数据示例如下:
| 名称 | 值 | 描述 |
| ---- | ----- | ----------- |
| `thermostat_temperature` | 18°C | 恒温器内置温度传感器测量的温度 |
| `livingroom_temperature` | 19°C | 一个远程温度传感器测量的温度,该传感器被命名为 `livingroom` 以标识它所在的房间 |
| `bedroom_temperature` | 21°C | 一个远程温度传感器测量的温度,该传感器被命名为 `bedroom` 以标识它所在的房间 |
云服务可以使用这些遥测数据来决定发送哪些命令以控制加热。
### 从 IoT 设备发送遥测数据
为夜灯添加互联网控制的下一步是将光线强度的遥测数据发送到 MQTT 代理的遥测主题。
#### 任务 - 从 IoT 设备发送遥测数据
将光线强度的遥测数据发送到 MQTT 代理。
数据以 JSON 格式编码——JSON 是 JavaScript 对象表示法的缩写,是一种使用键值对以文本形式编码数据的标准。
✅ 如果您之前没有接触过 JSON可以在 [JSON.org 文档](https://www.json.org/) 中了解更多信息。
按照以下相关步骤从设备发送遥测数据到 MQTT 代理:
* [Arduino - Wio Terminal](wio-terminal-telemetry.md)
* [单板计算机 - Raspberry Pi/虚拟 IoT 设备](single-board-computer-telemetry.md)
### 从 MQTT 代理接收遥测数据
发送遥测数据的意义在于有接收端来监听它。光线强度的遥测数据需要有程序监听并处理这些数据。这个“服务器”代码是您将在更大的 IoT 应用中部署到云服务的代码,但在这里,您将本地运行该代码(或直接在您的 Pi 上运行)。服务器代码由一个 Python 应用组成,它通过 MQTT 监听光线强度的遥测消息。在本课的后续部分,您将让它回复命令消息,指示 LED 开启或关闭。
✅ 做一些研究如果没有监听器MQTT 消息会发生什么?
#### 安装 Python 和 VS Code
如果您本地没有安装 Python 和 VS Code您需要安装它们以编写服务器代码。如果您使用虚拟 IoT 设备或在 Raspberry Pi 上工作,可以跳过此步骤,因为您应该已经安装并配置了这些工具。
##### 任务 - 安装 Python 和 VS Code
安装 Python 和 VS Code。
1. 安装 Python。请参考 [Python 下载页面](https://www.python.org/downloads/) 以获取安装最新版本 Python 的说明。
2. 安装 Visual Studio Code (VS Code)。这是您将用来编写 Python 虚拟设备代码的编辑器。请参考 [VS Code 文档](https://code.visualstudio.com?WT.mc_id=academic-17441-jabenn) 以获取安装 VS Code 的说明。
💁 如果你有自己喜欢的工具,可以自由使用任何 Python IDE 或编辑器来完成这些课程,但课程中的指示将基于使用 VS Code。
1. 安装 VS Code 的 Pylance 扩展。这是一个为 VS Code 提供 Python 语言支持的扩展。请参考 [Pylance 扩展文档](https://marketplace.visualstudio.com/items?WT.mc_id=academic-17441-jabenn&itemName=ms-python.vscode-pylance) 了解如何在 VS Code 中安装此扩展。
#### 配置 Python 虚拟环境
Python 的一个强大功能是可以安装 [pip 包](https://pypi.org)——这些是由其他人编写并发布到互联网的代码包。你可以通过一个命令将 pip 包安装到你的电脑上,然后在代码中使用该包。接下来你将使用 pip 安装一个用于通过 MQTT 通信的包。
默认情况下,当你安装一个包时,它会在你的电脑上全局可用,这可能会导致包版本冲突的问题——例如,一个应用程序依赖于某个版本的包,而另一个应用程序安装了新版本后可能会导致前一个应用程序无法正常运行。为了解决这个问题,你可以使用 [Python 虚拟环境](https://docs.python.org/3/library/venv.html),它本质上是一个专用文件夹中的 Python 副本,当你安装 pip 包时,它们只会安装到该文件夹中。
##### 任务 - 配置 Python 虚拟环境
配置一个 Python 虚拟环境并安装 MQTT 的 pip 包。
1. 在终端或命令行中,运行以下命令,在你选择的位置创建并导航到一个新目录:
```sh
mkdir nightlight-server
cd nightlight-server
```
1. 现在运行以下命令,在 `.venv` 文件夹中创建一个虚拟环境:
```sh
python3 -m venv .venv
```
> 💁 你需要显式调用 `python3` 来创建虚拟环境,以防你的系统同时安装了 Python 2 和 Python 3最新版本。如果安装了 Python 2调用 `python` 会使用 Python 2 而不是 Python 3。
1. 激活虚拟环境:
* 在 Windows 上:
* 如果你使用的是命令提示符或通过 Windows Terminal 使用命令提示符,请运行:
```cmd
.venv\Scripts\activate.bat
```
* 如果你使用的是 PowerShell请运行
```powershell
.\.venv\Scripts\Activate.ps1
```
* 在 macOS 或 Linux 上,请运行:
```cmd
source ./.venv/bin/activate
```
> 💁 这些命令应该在你运行创建虚拟环境的命令的同一位置运行。你永远不需要导航到 `.venv` 文件夹中,你应该始终从创建虚拟环境时所在的文件夹运行激活命令以及安装包或运行代码的命令。
1. 激活虚拟环境后,默认的 `python` 命令将运行用于创建虚拟环境的 Python 版本。运行以下命令查看版本:
```sh
python --version
```
输出将类似于以下内容:
```output
(.venv) ➜ nightlight-server python --version
Python 3.9.1
```
> 💁 你的 Python 版本可能不同——只要是 3.6 或更高版本即可。如果不是,请删除此文件夹,安装更新版本的 Python然后重试。
1. 运行以下命令安装 [Paho-MQTT](https://pypi.org/project/paho-mqtt/) 的 pip 包,这是一个流行的 MQTT 库。
```sh
pip install paho-mqtt
```
此 pip 包将仅安装在虚拟环境中,无法在虚拟环境之外使用。
#### 编写服务器代码
现在可以用 Python 编写服务器代码。
##### 任务 - 编写服务器代码
编写服务器代码。
1. 在虚拟环境中,从终端或命令行运行以下命令创建一个名为 `app.py` 的 Python 文件:
* 在 Windows 上运行:
```cmd
type nul > app.py
```
* 在 macOS 或 Linux 上运行:
```cmd
touch app.py
```
1. 在 VS Code 中打开当前文件夹:
```sh
code .
```
1. 当 VS Code 启动时,它会激活 Python 虚拟环境。这将在底部状态栏中显示:
![VS Code 显示选定的虚拟环境](../../../../../translated_images/zh-CN/vscode-virtual-env.8ba42e04c3d533cf.webp)
1. 如果 VS Code 启动时终端已经运行,它不会在终端中激活虚拟环境。最简单的方法是使用 **终止活动终端实例** 按钮关闭终端:
![VS Code 终止活动终端实例按钮](../../../../../translated_images/zh-CN/vscode-kill-terminal.1cc4de7c6f25ee08.webp)
1. 通过选择 *终端 -> 新终端* 或按 `` CTRL+` `` 启动一个新的 VS Code 终端。新终端将加载虚拟环境,激活命令会显示在终端中。虚拟环境的名称(`.venv`)也会显示在提示符中:
```output
➜ nightlight-server source .venv/bin/activate
(.venv) ➜ nightlight
```
1. 从 VS Code 的资源管理器中打开 `app.py` 文件并添加以下代码:
```python
import json
import time
import paho.mqtt.client as mqtt
id = '<ID>'
client_telemetry_topic = id + '/telemetry'
client_name = id + 'nightlight_server'
mqtt_client = mqtt.Client(client_name)
mqtt_client.connect('test.mosquitto.org')
mqtt_client.loop_start()
def handle_telemetry(client, userdata, message):
payload = json.loads(message.payload.decode())
print("Message received:", payload)
mqtt_client.subscribe(client_telemetry_topic)
mqtt_client.on_message = handle_telemetry
while True:
time.sleep(2)
```
将第 6 行中的 `<ID>` 替换为你创建设备代码时使用的唯一 ID。
⚠️ 这 **必须** 是你在设备上使用的相同 ID否则服务器代码将无法订阅或发布到正确的主题。
此代码创建了一个具有唯一名称的 MQTT 客户端,并连接到 *test.mosquitto.org* 代理。然后它启动一个处理循环,在后台线程中运行,监听任何订阅主题上的消息。
客户端随后订阅遥测主题上的消息,并定义一个函数,当接收到消息时调用该函数。当接收到遥测消息时,将调用 `handle_telemetry` 函数,将接收到的消息打印到控制台。
最后一个无限循环保持应用程序运行。MQTT 客户端在后台线程中监听消息,并在主应用程序运行时始终运行。
1. 从 VS Code 终端运行以下命令以运行你的 Python 应用程序:
```sh
python app.py
```
应用程序将开始监听来自 IoT 设备的消息。
1. 确保你的设备正在运行并发送遥测消息。调整物理或虚拟设备检测到的光线水平。接收到的消息将打印到终端。
```output
(.venv) ➜ nightlight-server python app.py
Message received: {'light': 0}
Message received: {'light': 400}
```
nightlight 虚拟环境中的 app.py 文件必须运行nightlight-server 虚拟环境中的 app.py 文件才能接收发送的消息。
> 💁 你可以在 [code-server/server](../../../../../1-getting-started/lessons/4-connect-internet/code-server/server) 文件夹中找到此代码。
### 遥测数据应该多久发送一次?
关于遥测数据的一个重要考虑是应该多久测量并发送一次数据?答案是——视情况而定。如果频繁测量,你可以更快地响应测量变化,但会消耗更多电量、带宽,生成更多数据,并需要更多云资源来处理。你需要测量得足够频繁,但不能过于频繁。
对于恒温器,每隔几分钟测量一次可能已经足够,因为温度变化并不频繁。如果你一天只测量一次,那么可能会在阳光明媚的白天因夜间温度而加热房屋,而如果你每秒测量一次,你将生成数千个不必要的重复温度测量,这会占用用户的互联网速度和带宽(对于有限带宽计划的用户来说是个问题),消耗更多电量,这对像远程传感器这样的电池供电设备来说可能是个问题,并增加提供商处理和存储云计算资源的成本。
如果你在监控工厂中某台机器的数据,而如果它发生故障可能会造成灾难性损失和数百万美元的收入损失,那么可能需要每秒多次测量。浪费带宽总比错过表明机器需要停止并修复的遥测数据要好。
> 💁 在这种情况下,你可能会考虑使用边缘设备先处理遥测数据,以减少对互联网的依赖。
### 连接丢失
互联网连接可能不可靠断网是常见现象。在这种情况下IoT 设备应该怎么做——是丢失数据,还是存储数据直到连接恢复?答案同样是视情况而定。
对于恒温器,数据可以在新的温度测量完成后立即丢弃。加热系统不关心 20 分钟前的温度是 20.5°C如果现在的温度是 19°C那么当前的温度决定了加热是否应该开启或关闭。
对于机器,你可能希望保留数据,尤其是如果数据用于趋势分析。有些机器学习模型可以通过查看定义时间段(例如过去一小时)的数据流并发现异常数据来检测异常。这通常用于预测性维护,寻找可能即将发生故障的迹象,以便在故障发生前进行修复或更换。你可能希望发送机器的每一条遥测数据,以便处理异常检测,因此一旦 IoT 设备可以重新连接,它将发送断网期间生成的所有遥测数据。
IoT 设备设计者还应该考虑 IoT 设备在断网或因位置导致信号丢失时是否可以使用。智能恒温器应该能够在无法将遥测数据发送到云端时做出一些有限的决策来控制加热。
[![这辆法拉利因为有人试图在没有手机信号的地下升级而被锁死](../../../../../translated_images/zh-CN/bricked-car.dc38f8efadc6c59d76211f981a521efb300939283dee468f79503aae3ec67615.png)](https://twitter.com/internetofshit/status/1315736960082808832)
对于 MQTT 来处理连接丢失,设备和服务器代码需要负责确保消息传递的可靠性,例如要求所有发送的消息都通过回复主题上的额外消息进行回复,如果没有,则手动排队以便稍后重播。
## 命令
命令是从云端发送到设备的消息,指示设备执行某些操作。大多数情况下,这涉及通过执行器提供某种输出,但也可以是设备本身的指令,例如重启或收集额外的遥测数据并将其作为命令的响应返回。
![一个连接到互联网的恒温器接收到打开加热的命令](../../../../../translated_images/zh-CN/commands.d6c06bbbb3a02cce95f2831a1c331daf6dedd4e470c4aa2b0ae54f332016e504.png)
恒温器可以从云端接收到打开加热的命令。根据所有传感器的遥测数据,如果云服务决定加热应该开启,它会发送相关命令。
### 向 MQTT 代理发送命令
我们下一步的互联网控制夜灯是让服务器代码根据检测到的光线水平向 IoT 设备发送命令以控制灯光。
1. 在 VS Code 中打开服务器代码
1. 在声明 `client_telemetry_topic` 后添加以下行以定义发送命令的主题:
```python
server_command_topic = id + '/commands'
```
1. 在 `handle_telemetry` 函数末尾添加以下代码:
```python
command = { 'led_on' : payload['light'] < 300 }
print("Sending message:", command)
client.publish(server_command_topic, json.dumps(command))
```
这会向命令主题发送一个 JSON 消息,其中 `led_on` 的值根据光线是否小于 300 设置为 true 或 false。如果光线小于 300则发送 true 指示设备打开 LED。
1. 像之前一样运行代码
1. 调整物理或虚拟设备检测到的光线水平。接收到的消息和发送的命令将写入终端:
```output
(.venv) ➜ nightlight-server python app.py
Message received: {'light': 0}
Sending message: {'led_on': True}
Message received: {'light': 400}
Sending message: {'led_on': False}
```
> 💁 遥测数据和命令分别在单个主题上发送。这意味着来自多个设备的遥测数据将出现在同一个遥测主题上,而发送给多个设备的命令将出现在同一个命令主题上。如果你想向特定设备发送命令,可以使用多个主题,以唯一设备 ID 命名,例如 `/commands/device1``/commands/device2`。这样设备可以只监听专门为该设备发送的消息。
> 💁 你可以在 [code-commands/server](../../../../../1-getting-started/lessons/4-connect-internet/code-commands/server) 文件夹中找到此代码。
### 在 IoT 设备上处理命令
现在服务器已经开始发送命令,你可以在 IoT 设备上添加代码来处理这些命令并控制 LED。
按照以下相关步骤从 MQTT 代理监听命令:
* [Arduino - Wio Terminal](wio-terminal-commands.md)
* [单板计算机 - Raspberry Pi/虚拟 IoT 设备](single-board-computer-commands.md)
一旦代码编写并运行,尝试改变光线水平。观察服务器和设备的输出,并在改变光线水平时观察 LED 的变化。
### 连接丢失
如果云服务需要向离线的 IoT 设备发送命令,它应该怎么做?答案同样是视情况而定。
如果最新命令覆盖了较早的命令,那么较早的命令可能可以忽略。如果云服务发送了一个打开加热的命令,然后发送了一个关闭加热的命令,那么可以忽略打开命令而不重新发送。
如果命令需要按顺序处理,例如移动机器人手臂向上,然后关闭抓手,那么它们需要在连接恢复后按顺序发送。
✅ 设备或服务器代码如何确保命令始终按顺序发送和处理(如果需要)?
---
## 🚀 挑战
在过去三节课的挑战中,列出你家里、学校或工作场所中的尽可能多的 IoT 设备,并判断它们是基于微控制器还是单板计算机,或者两者的混合体,并思考它们使用了哪些传感器和执行器。
对于这些设备,思考它们可能发送或接收什么消息。它们发送什么遥测数据?可能接收什么消息或命令?你认为它们安全吗?
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/8)
## 复习与自学
阅读更多关于MQTT的信息请访问 [MQTT维基百科页面](https://wikipedia.org/wiki/MQTT)。
尝试自己运行一个MQTT代理使用 [Mosquitto](https://www.mosquitto.org),并从你的物联网设备和服务器代码连接到它。
> 💁 提示 - 默认情况下Mosquitto不允许匿名连接即没有用户名和密码的连接也不允许来自运行它的计算机外部的连接。
> 你可以通过一个 [`mosquitto.conf` 配置文件](https://www.mosquitto.org/man/mosquitto-conf-5.html)解决这个问题,内容如下:
>
> ```sh
> listener 1883 0.0.0.0
> allow_anonymous true
> ```
## 作业
[比较和对比MQTT与其他通信协议](assignment.md)
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,17 @@
# 比较和对比 MQTT 与其他通信协议
## 指导
本课程介绍了 MQTT 作为一种通信协议。除此之外,还有其他协议,例如 AMQP 和 HTTP/HTTPS。
请研究这些协议,并将它们与 MQTT 进行比较和对比。考虑功耗、安全性以及在连接丢失时的消息持久性。
## 评分标准
| 标准 | 卓越 | 合格 | 需要改进 |
| -------- | --------- | -------- | ----------------- |
| 比较 AMQP 与 MQTT | 能够比较和对比 AMQP 与 MQTT并涵盖功耗、安全性和消息持久性。 | 能够部分比较和对比 AMQP 与 MQTT并涵盖功耗、安全性和消息持久性中的两项。 | 能够部分比较和对比 AMQP 与 MQTT并涵盖功耗、安全性和消息持久性中的一项。 |
| 比较 HTTP/HTTPS 与 MQTT | 能够比较和对比 HTTP/HTTPS 与 MQTT并涵盖功耗、安全性和消息持久性。 | 能够部分比较和对比 HTTP/HTTPS 与 MQTT并涵盖功耗、安全性和消息持久性中的两项。 | 能够部分比较和对比 HTTP/HTTPS 与 MQTT并涵盖功耗、安全性和消息持久性中的一项。 |
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,56 @@
# 通过互联网控制你的夜灯 - 虚拟物联网硬件和树莓派
在本课的这一部分,你将订阅从 MQTT broker 发送到你的树莓派或虚拟物联网设备的命令。
## 订阅命令
下一步是订阅从 MQTT broker 发送的命令并对其作出响应。
### 任务
订阅命令。
1. 在 VS Code 中打开夜灯项目。
1. 如果你使用的是虚拟物联网设备,请确保终端正在运行虚拟环境。如果你使用的是树莓派,则无需使用虚拟环境。
1. 在 `client_telemetry_topic` 定义之后添加以下代码:
```python
server_command_topic = id + '/commands'
```
`server_command_topic` 是设备订阅以接收 LED 命令的 MQTT 主题。
1. 在主循环的上方、`mqtt_client.loop_start()` 行之后添加以下代码:
```python
def handle_command(client, userdata, message):
payload = json.loads(message.payload.decode())
print("Message received:", payload)
if payload['led_on']:
led.on()
else:
led.off()
mqtt_client.subscribe(server_command_topic)
mqtt_client.on_message = handle_command
```
这段代码定义了一个函数 `handle_command`,它将消息读取为 JSON 文档,并查找 `led_on` 属性的值。如果该值为 `True`,则打开 LED否则关闭 LED。
MQTT 客户端会订阅服务器发送消息的主题,并设置在接收到消息时调用 `handle_command` 函数。
> 💁 `on_message` 处理器会被所有订阅的主题调用。如果你之后编写代码监听多个主题,可以从传递给处理器函数的 `message` 对象中获取消息发送的主题。
1. 按照上一部分作业运行代码的方式运行代码。如果你使用的是虚拟物联网设备,请确保 CounterFit 应用正在运行,并且光传感器和 LED 已在正确的引脚上创建。
1. 调整你的物理或虚拟设备检测到的光线水平。接收到的消息和发送的命令会写入终端。LED 也会根据光线水平打开或关闭。
> 💁 你可以在 [code-commands/virtual-device](../../../../../1-getting-started/lessons/4-connect-internet/code-commands/virtual-device) 文件夹或 [code-commands/pi](../../../../../1-getting-started/lessons/4-connect-internet/code-commands/pi) 文件夹中找到这段代码。
😀 恭喜!你已经成功编写了设备代码,使其能够响应来自 MQTT broker 的命令。
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而导致的任何误解或误读,我们概不负责。

@ -0,0 +1,82 @@
# 通过互联网控制你的夜灯 - 虚拟物联网硬件和树莓派
物联网设备需要通过 MQTT 与 *test.mosquitto.org* 通信,以发送包含光传感器读数的遥测值,并接收控制 LED 的命令。
在本课程的这一部分,你将把树莓派或虚拟物联网设备连接到 MQTT broker。
## 安装 MQTT 客户端包
为了与 MQTT broker 通信,你需要在树莓派或虚拟设备的虚拟环境中安装一个 MQTT 库的 pip 包。
### 任务
安装 pip 包
1. 在 VS Code 中打开夜灯项目。
1. 如果你使用的是虚拟物联网设备,请确保终端正在运行虚拟环境。如果你使用的是树莓派,则无需使用虚拟环境。
1. 运行以下命令以安装 MQTT pip 包:
```sh
pip3 install paho-mqtt
```
## 编写设备代码
设备已经准备好进行编码。
### 任务
编写设备代码。
1. 在 `app.py` 文件顶部添加以下导入:
```python
import paho.mqtt.client as mqtt
```
`paho.mqtt.client` 库允许你的应用通过 MQTT 进行通信。
1. 在光传感器和 LED 的定义之后添加以下代码:
```python
id = '<ID>'
client_name = id + 'nightlight_client'
```
`<ID>` 替换为一个唯一的 ID该 ID 将用作此设备客户端的名称,并在后续用于此设备发布和订阅的主题名称。*test.mosquitto.org* broker 是公共的,许多人都在使用,包括其他完成此任务的学生。拥有一个唯一的 MQTT 客户端名称和主题名称可以确保你的代码不会与其他人的代码冲突。在稍后的任务中创建服务器代码时,你也需要使用此 ID。
> 💁 你可以使用像 [GUIDGen](https://www.guidgen.com) 这样的网站生成一个唯一的 ID。
`client_name` 是此 MQTT 客户端在 broker 上的唯一名称。
1. 在这段新代码的下方添加以下代码,以创建一个 MQTT 客户端对象并连接到 MQTT broker
```python
mqtt_client = mqtt.Client(client_name)
mqtt_client.connect('test.mosquitto.org')
mqtt_client.loop_start()
print("MQTT connected!")
```
此代码创建客户端对象,连接到公共 MQTT broker并启动一个处理循环该循环在后台线程中运行监听任何已订阅主题的消息。
1. 以与完成任务的上一部分代码相同的方式运行代码。如果你使用的是虚拟物联网设备,请确保 CounterFit 应用正在运行,并且光传感器和 LED 已在正确的引脚上创建。
```output
(.venv) ➜ nightlight python app.py
MQTT connected!
Light level: 0
Light level: 0
```
> 💁 你可以在 [code-mqtt/virtual-device](../../../../../1-getting-started/lessons/4-connect-internet/code-mqtt/virtual-device) 文件夹或 [code-mqtt/pi](../../../../../1-getting-started/lessons/4-connect-internet/code-mqtt/pi) 文件夹中找到此代码。
😀 你已成功将设备连接到 MQTT broker。
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而导致的任何误解或误读,我们概不负责。

@ -0,0 +1,63 @@
# 通过互联网控制夜灯 - 虚拟物联网硬件和树莓派
在本节课程中,您将从树莓派或虚拟物联网设备向 MQTT broker 发送光照水平的遥测数据。
## 发布遥测数据
下一步是创建一个包含遥测数据的 JSON 文档,并将其发送到 MQTT broker。
### 任务
将遥测数据发布到 MQTT broker。
1. 在 VS Code 中打开夜灯项目。
1. 如果您使用的是虚拟物联网设备,请确保终端正在运行虚拟环境。如果您使用的是树莓派,则无需使用虚拟环境。
1. 在 `app.py` 文件顶部添加以下导入:
```python
import json
```
`json` 库用于将遥测数据编码为 JSON 文档。
1. 在 `client_name` 声明之后添加以下内容:
```python
client_telemetry_topic = id + '/telemetry'
```
`client_telemetry_topic` 是设备将光照水平发布到的 MQTT 主题。
1. 将文件末尾的 `while True:` 循环内容替换为以下内容:
```python
while True:
light = light_sensor.light
telemetry = json.dumps({'light' : light})
print("Sending telemetry ", telemetry)
mqtt_client.publish(client_telemetry_topic, telemetry)
time.sleep(5)
```
此代码将光照水平打包为 JSON 文档并发布到 MQTT broker。然后它会休眠以减少消息发送的频率。
1. 以与之前任务相同的方式运行代码。如果您使用的是虚拟物联网设备,请确保 CounterFit 应用正在运行,并且光传感器和 LED 已在正确的引脚上创建。
```output
(.venv) ➜ nightlight python app.py
MQTT connected!
Sending telemetry {"light": 0}
Sending telemetry {"light": 0}
```
> 💁 您可以在 [code-telemetry/virtual-device](../../../../../1-getting-started/lessons/4-connect-internet/code-telemetry/virtual-device) 文件夹或 [code-telemetry/pi](../../../../../1-getting-started/lessons/4-connect-internet/code-telemetry/pi) 文件夹中找到此代码。
😀 您已成功从设备发送遥测数据。
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,82 @@
# 通过互联网控制夜灯 - Wio Terminal
在本节课程中,您将订阅从 MQTT broker 发送到 Wio Terminal 的命令。
## 订阅命令
下一步是订阅从 MQTT broker 发送的命令,并对其作出响应。
### 任务
订阅命令。
1. 在 VS Code 中打开夜灯项目。
1. 在 `config.h` 文件的底部添加以下代码,以定义命令的主题名称:
```cpp
const string SERVER_COMMAND_TOPIC = ID + "/commands";
```
`SERVER_COMMAND_TOPIC` 是设备将订阅以接收 LED 命令的主题。
1. 在 `reconnectMQTTClient` 函数的末尾添加以下代码行,以便在 MQTT 客户端重新连接时订阅命令主题:
```cpp
client.subscribe(SERVER_COMMAND_TOPIC.c_str());
```
1. 在 `reconnectMQTTClient` 函数下方添加以下代码:
```cpp
void clientCallback(char *topic, uint8_t *payload, unsigned int length)
{
char buff[length + 1];
for (int i = 0; i < length; i++)
{
buff[i] = (char)payload[i];
}
buff[length] = '\0';
Serial.print("Message received:");
Serial.println(buff);
DynamicJsonDocument doc(1024);
deserializeJson(doc, buff);
JsonObject obj = doc.as<JsonObject>();
bool led_on = obj["led_on"];
if (led_on)
digitalWrite(D0, HIGH);
else
digitalWrite(D0, LOW);
}
```
此函数将作为回调,当 MQTT 客户端从服务器接收到消息时调用。
消息以无符号 8 位整数数组的形式接收,因此需要转换为字符数组才能作为文本处理。
消息包含一个 JSON 文档,并使用 ArduinoJson 库进行解码。读取 JSON 文档的 `led_on` 属性,根据其值决定是否打开或关闭 LED。
1. 在 `createMQTTClient` 函数中添加以下代码:
```cpp
client.setCallback(clientCallback);
```
此代码将 `clientCallback` 设置为当从 MQTT broker 接收到消息时调用的回调函数。
> 💁 `clientCallback` 处理程序会为所有订阅的主题调用。如果您稍后编写监听多个主题的代码,可以通过回调函数传递的 `topic` 参数获取消息发送的主题。
1. 将代码上传到您的 Wio Terminal并使用串口监视器查看发送到 MQTT broker 的光线强度数据。
1. 调整您的物理或虚拟设备检测到的光线强度。您将在终端中看到接收到的消息和发送的命令。根据光线强度,您还会看到 LED 被打开或关闭。
> 💁 您可以在 [code-commands/wio-terminal](../../../../../1-getting-started/lessons/4-connect-internet/code-commands/wio-terminal) 文件夹中找到此代码。
😀 您已成功编写代码,使设备能够响应来自 MQTT broker 的命令。
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们不对因使用此翻译而产生的任何误解或误读承担责任。

@ -0,0 +1,240 @@
# 通过互联网控制夜灯 - Wio Terminal
需要为物联网设备编写代码,使其通过 MQTT 与 *test.mosquitto.org* 通信,发送包含光传感器读数的遥测值,并接收控制 LED 的命令。
在本节课程中,您将把 Wio Terminal 连接到一个 MQTT 代理。
## 安装 WiFi 和 MQTT Arduino 库
为了与 MQTT 代理通信,您需要安装一些 Arduino 库,以便使用 Wio Terminal 的 WiFi 芯片并与 MQTT 通信。在为 Arduino 设备开发时您可以使用大量包含开源代码的库这些库实现了广泛的功能。Seeed 发布了适用于 Wio Terminal 的库,使其能够通过 WiFi 通信。其他开发者也发布了与 MQTT 代理通信的库,您将使用这些库与设备进行交互。
这些库以源代码的形式提供,可以自动导入到 PlatformIO 中并为您的设备编译。通过这种方式Arduino 库可以在支持 Arduino 框架的任何设备上运行前提是设备具备该库所需的特定硬件。例如Seeed WiFi 库是针对特定硬件的。
库可以全局安装并在需要时编译,也可以安装到特定项目中。在本次任务中,库将被安装到项目中。
✅ 您可以在 [PlatformIO 库文档](https://docs.platformio.org/en/latest/librarymanager/index.html) 中了解更多关于库管理以及如何查找和安装库的信息。
### 任务 - 安装 WiFi 和 MQTT Arduino 库
安装 Arduino 库。
1. 在 VS Code 中打开夜灯项目。
1. 在 `platformio.ini` 文件的末尾添加以下内容:
```ini
lib_deps =
seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5
seeed-studio/Seeed Arduino FS @ 2.1.1
seeed-studio/Seeed Arduino SFUD @ 2.0.2
seeed-studio/Seeed Arduino rpcUnified @ 2.1.3
seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1
```
这将导入 Seeed WiFi 库。`@ <number>` 语法指的是库的特定版本号。
> 💁 您可以删除 `@ <number>` 以始终使用库的最新版本,但无法保证后续版本能与以下代码正常工作。这里的代码已使用该版本的库进行了测试。
这就是添加库所需的全部操作。下次 PlatformIO 构建项目时,它将下载这些库的源代码并将其编译到您的项目中。
1. 在 `lib_deps` 中添加以下内容:
```ini
knolleary/PubSubClient @ 2.8
```
这将导入 [PubSubClient](https://github.com/knolleary/pubsubclient),一个 Arduino MQTT 客户端。
## 连接到 WiFi
现在可以将 Wio Terminal 连接到 WiFi。
### 任务 - 连接到 WiFi
将 Wio Terminal 连接到 WiFi。
1. 在 `src` 文件夹中创建一个名为 `config.h` 的新文件。您可以通过选择 `src` 文件夹或其中的 `main.cpp` 文件,然后从资源管理器中选择 **新建文件** 按钮来完成此操作。此按钮仅在光标悬停在资源管理器上时显示。
![新建文件按钮](../../../../../translated_images/zh-CN/vscode-new-file-button.182702340fe6723c.webp)
1. 在此文件中添加以下代码以定义 WiFi 凭据的常量:
```cpp
#pragma once
#include <string>
using namespace std;
// WiFi credentials
const char *SSID = "<SSID>";
const char *PASSWORD = "<PASSWORD>";
```
`<SSID>` 替换为您的 WiFi SSID`<PASSWORD>` 替换为您的 WiFi 密码。
1. 打开 `main.cpp` 文件。
1. 在文件顶部添加以下 `#include` 指令:
```cpp
#include <PubSubClient.h>
#include <rpcWiFi.h>
#include <SPI.h>
#include "config.h"
```
这将包含您之前添加的库的头文件以及配置头文件。这些头文件用于告诉 PlatformIO 引入库中的代码。如果未显式包含这些头文件,某些代码将不会被编译,您会遇到编译错误。
1. 在 `setup` 函数上方添加以下代码:
```cpp
void connectWiFi()
{
while (WiFi.status() != WL_CONNECTED)
{
Serial.println("Connecting to WiFi..");
WiFi.begin(SSID, PASSWORD);
delay(500);
}
Serial.println("Connected!");
}
```
这段代码会在设备未连接到 WiFi 时循环,并尝试使用配置头文件中的 SSID 和密码进行连接。
1. 在 `setup` 函数底部、引脚配置完成后调用此函数。
```cpp
connectWiFi();
```
1. 将此代码上传到您的设备以检查 WiFi 连接是否正常工作。您应该在串行监视器中看到以下内容:
```output
> Executing task: platformio device monitor <
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at http://bit.ly/pio-monitor-filters
--- Miniterm on /dev/cu.usbmodem1101 9600,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
Connecting to WiFi..
Connected!
```
## 连接到 MQTT
一旦 Wio Terminal 连接到 WiFi就可以连接到 MQTT 代理。
### 任务 - 连接到 MQTT
连接到 MQTT 代理。
1. 在 `config.h` 文件底部添加以下代码以定义 MQTT 代理的连接详细信息:
```cpp
// MQTT settings
const string ID = "<ID>";
const string BROKER = "test.mosquitto.org";
const string CLIENT_NAME = ID + "nightlight_client";
```
`<ID>` 替换为一个唯一的 ID该 ID 将用作此设备客户端的名称,并在稍后用于设备发布和订阅的主题。*test.mosquitto.org* 代理是公共的,许多人都会使用,包括其他完成此任务的学生。拥有唯一的 MQTT 客户端名称和主题名称可以确保您的代码不会与其他人的代码冲突。稍后在创建服务器代码时,您也需要此 ID。
> 💁 您可以使用类似 [GUIDGen](https://www.guidgen.com) 的网站生成一个唯一 ID。
`BROKER` 是 MQTT 代理的 URL。
`CLIENT_NAME` 是此 MQTT 客户端在代理上的唯一名称。
1. 打开 `main.cpp` 文件,在 `connectWiFi` 函数下方、`setup` 函数上方添加以下代码:
```cpp
WiFiClient wioClient;
PubSubClient client(wioClient);
```
这段代码使用 Wio Terminal WiFi 库创建一个 WiFi 客户端,并使用它创建一个 MQTT 客户端。
1. 在此代码下方添加以下内容:
```cpp
void reconnectMQTTClient()
{
while (!client.connected())
{
Serial.print("Attempting MQTT connection...");
if (client.connect(CLIENT_NAME.c_str()))
{
Serial.println("connected");
}
else
{
Serial.print("Retying in 5 seconds - failed, rc=");
Serial.println(client.state());
delay(5000);
}
}
}
```
此函数测试与 MQTT 代理的连接,如果未连接则重新连接。它会在未连接时循环并尝试使用配置头文件中定义的唯一客户端名称进行连接。
如果连接失败,它会在 5 秒后重试。
1. 在 `reconnectMQTTClient` 函数下方添加以下代码:
```cpp
void createMQTTClient()
{
client.setServer(BROKER.c_str(), 1883);
reconnectMQTTClient();
}
```
这段代码为客户端设置 MQTT 代理,并设置接收到消息时的回调函数。然后尝试连接到代理。
1. 在 WiFi 连接完成后,在 `setup` 函数中调用 `createMQTTClient` 函数。
1. 将整个 `loop` 函数替换为以下内容:
```cpp
void loop()
{
reconnectMQTTClient();
client.loop();
delay(2000);
}
```
这段代码首先重新连接到 MQTT 代理。这些连接很容易中断,因此定期检查并在必要时重新连接是值得的。然后调用 MQTT 客户端的 `loop` 方法来处理订阅主题上收到的任何消息。此应用是单线程的,因此无法在后台线程接收消息,因此需要在主线程上分配时间来处理网络连接上等待的任何消息。
最后2 秒的延迟确保光线水平不会发送得太频繁,从而降低设备的功耗。
1. 将代码上传到您的 Wio Terminal并使用串行监视器查看设备连接到 WiFi 和 MQTT。
```output
> Executing task: platformio device monitor <
source /Users/jimbennett/GitHub/IoT-For-Beginners/1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal/nightlight/.venv/bin/activate
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at http://bit.ly/pio-monitor-filters
--- Miniterm on /dev/cu.usbmodem1201 9600,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
Connecting to WiFi..
Connected!
Attempting MQTT connection...connected
```
> 💁 您可以在 [code-mqtt/wio-terminal](../../../../../1-getting-started/lessons/4-connect-internet/code-mqtt/wio-terminal) 文件夹中找到此代码。
😀 您已成功将设备连接到 MQTT 代理。
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,82 @@
# 通过互联网控制夜灯 - Wio Terminal
在本节课程中,您将把 Wio Terminal 的光照强度数据作为遥测信息发送到 MQTT 代理。
## 安装 JSON Arduino 库
使用 JSON 是通过 MQTT 发送消息的一种常见方式。Arduino 提供了一个 JSON 库,可以更轻松地读取和写入 JSON 文档。
### 任务
安装 Arduino JSON 库。
1. 在 VS Code 中打开夜灯项目。
1. 在 `platformio.ini` 文件的 `lib_deps` 列表中添加以下内容作为额外一行:
```ini
bblanchon/ArduinoJson @ 6.17.3
```
这将导入 [ArduinoJson](https://arduinojson.org),一个 Arduino 的 JSON 库。
## 发布遥测数据
下一步是创建一个包含遥测数据的 JSON 文档,并将其发送到 MQTT 代理。
### 任务 - 发布遥测数据
将遥测数据发布到 MQTT 代理。
1. 在 `config.h` 文件底部添加以下代码,以定义 MQTT 代理的遥测主题名称:
```cpp
const string CLIENT_TELEMETRY_TOPIC = ID + "/telemetry";
```
`CLIENT_TELEMETRY_TOPIC` 是设备将光照强度数据发布到的主题。
1. 打开 `main.cpp` 文件。
1. 在文件顶部添加以下 `#include` 指令:
```cpp
#include <ArduinoJSON.h>
```
1. 在 `loop` 函数中,在 `delay` 之前添加以下代码:
```cpp
int light = analogRead(WIO_LIGHT);
DynamicJsonDocument doc(1024);
doc["light"] = light;
string telemetry;
serializeJson(doc, telemetry);
Serial.print("Sending telemetry ");
Serial.println(telemetry.c_str());
client.publish(CLIENT_TELEMETRY_TOPIC.c_str(), telemetry.c_str());
```
这段代码读取光照强度,并使用 ArduinoJson 创建一个包含该强度的 JSON 文档。随后将其序列化为字符串,并通过 MQTT 客户端发布到遥测 MQTT 主题。
1. 将代码上传到您的 Wio Terminal并使用串口监视器查看发送到 MQTT 代理的光照强度数据。
```output
Connecting to WiFi..
Connected!
Attempting MQTT connection...connected
Sending telemetry {"light":652}
Sending telemetry {"light":612}
Sending telemetry {"light":583}
```
> 💁 您可以在 [code-telemetry/wio-terminal](../../../../../1-getting-started/lessons/4-connect-internet/code-telemetry/wio-terminal) 文件夹中找到这段代码。
😀 您已成功从设备发送遥测数据。
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。对于因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,23 @@
# 使用物联网进行农业种植
随着人口的增长对农业的需求也在增加。可用的土地面积并没有变化但气候却在不断变化——这给农民带来了更多的挑战尤其是那20亿依赖自给自足农业的农民他们靠自己种植的作物来维持生计并养活家人。物联网可以帮助农民更聪明地决定种植什么、何时收获提高产量减少体力劳动并检测和处理害虫问题。
在这6节课程中你将学习如何应用物联网来改进和自动化农业种植。
> 💁 这些课程将使用一些云资源。如果你没有完成本项目中的所有课程,请确保[清理你的项目](../clean-up.md)。
## 主题
1. [使用物联网预测植物生长](lessons/1-predict-plant-growth/README.md)
1. [检测土壤湿度](lessons/2-detect-soil-moisture/README.md)
1. [自动化植物浇水](lessons/3-automated-plant-watering/README.md)
1. [将你的植物迁移到云端](lessons/4-migrate-your-plant-to-the-cloud/README.md)
1. [将你的应用逻辑迁移到云端](lessons/5-migrate-application-to-the-cloud/README.md)
1. [保护你的植物安全](lessons/6-keep-your-plant-secure/README.md)
## 致谢
所有课程由 [Jim Bennett](https://GitHub.com/JimBobBennett) 倾注 ♥️ 编写完成。
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,273 @@
## 使用物联网预测植物生长
![本课的概述手绘图](../../../../../translated_images/zh-CN/lesson-5.42b234299279d263143148b88ab4583861a32ddb03110c6c1120e41bb88b2592.jpg)
> 手绘图由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看更大版本。
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/9)
## 简介
植物需要一些基本条件才能生长——水、二氧化碳、养分、光和热。在本课中,您将学习如何通过测量空气温度来计算植物的生长和成熟速度。
本课内容包括:
* [数字农业](../../../../../2-farm/lessons/1-predict-plant-growth)
* [为什么温度对农业很重要?](../../../../../2-farm/lessons/1-predict-plant-growth)
* [测量环境温度](../../../../../2-farm/lessons/1-predict-plant-growth)
* [生长度日 (GDD)](../../../../../2-farm/lessons/1-predict-plant-growth)
* [使用温度传感器数据计算 GDD](../../../../../2-farm/lessons/1-predict-plant-growth)
## 数字农业
数字农业正在改变我们的耕作方式,通过工具收集、存储和分析农业数据。我们目前正处于世界经济论坛所描述的“第四次工业革命”时期,而数字农业的兴起被称为“第四次农业革命”或“农业 4.0”。
> 🎓 数字农业这一术语还包括整个“农业价值链”,即从农场到餐桌的整个过程。这包括在食品运输和加工过程中跟踪产品质量、仓储和电子商务系统,甚至包括拖拉机租赁应用程序!
这些变化使农民能够提高产量,减少化肥和农药的使用,并更高效地利用水资源。虽然这些技术主要应用于较富裕的国家,但传感器和其他设备的价格正在逐渐下降,使其在发展中国家也变得更加可负担。
数字农业支持的一些技术包括:
* 温度测量——通过测量温度,农民可以预测植物的生长和成熟。
* 自动灌溉——通过测量土壤湿度并在土壤过于干燥时启动灌溉系统,而不是定时灌溉。定时灌溉可能导致在炎热干燥的天气下作物缺水,或在下雨时过度灌溉。通过仅在土壤需要时进行灌溉,农民可以优化水资源的使用。
* 害虫控制——农民可以使用自动机器人或无人机上的摄像头检查害虫,然后仅在需要的地方喷洒农药,从而减少农药的使用量,并减少农药流入当地水源的风险。
✅ 做一些研究。还有哪些技术可以用来提高农业产量?
> 🎓 “精准农业”这一术语用于定义基于每块田地甚至田地的部分进行观察、测量和响应。这包括测量水分、养分和害虫水平,并准确地做出反应,例如仅对田地的一小部分进行灌溉。
## 为什么温度对农业很重要?
在学习植物时,大多数学生都会了解到水、光、二氧化碳和养分的重要性。植物还需要温暖才能生长——这就是为什么植物会在春天随着气温升高而开花,为什么雪花莲或水仙花会因短暂的暖和天气而提前发芽,以及为什么温室和暖房能促进植物生长。
> 🎓 温室和暖房的功能类似,但有一个重要区别。暖房通过人工加热使农民能够更精确地控制温度,而温室依赖太阳提供热量,通常只能通过窗户或其他开口来调节温度。
植物有一个基础或最低温度、最佳温度和最高温度,这些都基于每日平均温度。
* 基础温度——这是植物生长所需的最低每日平均温度。
* 最佳温度——这是植物生长最快的最佳每日平均温度。
* 最高温度——这是植物能够承受的最高温度。超过这个温度,植物会停止生长以节约水分并维持生命。
> 💁 这些是平均温度,是日间和夜间温度的平均值。植物还需要昼夜不同的温度,以便更高效地进行光合作用并在夜间节约能量。
每种植物的基础、最佳和最高温度值都不同。这就是为什么有些植物在热带国家生长良好,而另一些植物在寒冷国家更适合。
✅ 做一些研究。对于您花园、学校或当地公园中的植物,看看是否能找到它们的基础温度。
![一张显示植物生长率随温度升高而增加,然后在温度过高时下降的图表](../../../../../translated_images/zh-CN/plant-growth-temp-graph.c6d69c9478e6ca83.webp)
上图显示了一个生长率与温度的示例图表。在基础温度以下没有生长。生长率在达到最佳温度之前逐渐增加,然后在达到峰值后下降。在最高温度时,生长停止。
这种图表的形状因植物种类而异。有些植物在最佳温度以上的下降更陡峭,有些从基础到最佳的增长更缓慢。
> 💁 农民要想获得最佳生长效果,需要了解植物的三个温度值,并理解它们的图表形状。
如果农民能够控制温度例如在商业暖房中他们就可以为植物优化环境。例如一个商业暖房种植番茄时白天的温度会设置在约25°C夜间则设置在约20°C以获得最快的生长速度。
> 🍅 将这些温度与人工光源、化肥和控制的二氧化碳水平结合起来,商业种植者可以全年种植和收获。
## 测量环境温度
温度传感器可以与物联网设备结合使用来测量环境温度。
### 任务 - 测量温度
按照相关指南使用您的物联网设备监测温度:
* [Arduino - Wio Terminal](wio-terminal-temp.md)
* [单板计算机 - Raspberry Pi](pi-temp.md)
* [单板计算机 - 虚拟设备](virtual-device-temp.md)
## 生长度日 (GDD)
生长度日(也称为生长度单位)是一种基于温度来衡量植物生长的方法。假设植物有足够的水分、养分和二氧化碳,温度决定了生长速度。
生长度日GDD每天计算为当天的平均温度以摄氏度为单位减去植物的基础温度。每种植物需要一定数量的 GDD 才能生长、开花或成熟并产出作物。每天的 GDD 越多,植物生长得越快。
> 🇺🇸 对于美国人来说生长度日也可以用华氏度计算。5 GDD以摄氏度计算相当于9 GDD以华氏度计算
完整的 GDD 公式稍显复杂,但通常使用一个简化的公式作为良好的近似值:
![GDD = T max + T min 除以 2然后减去 T base](../../../../../translated_images/zh-CN/gdd-calculation.79b3660f9c5757aa92dc2dd2cdde75344e2d2c1565c4b3151640f7887edc0275.png)
* **GDD** - 这是生长度日的数量
* **T max** - 这是当天的最高温度(摄氏度)
* **T min** - 这是当天的最低温度(摄氏度)
* **T base** - 这是植物的基础温度(摄氏度)
> 💁 有些公式会处理 T max 超过 30°C 或 T min 低于 T base 的情况,但我们暂时忽略这些。
### 示例 - 玉米 🌽
根据品种不同,玉米(或称为苞谷)需要 800 到 2,700 GDD 才能成熟,其基础温度为 10°C。
在基础温度以上的第一天,测得以下温度:
| 测量值 | 温度 °C |
| :------- | :-----: |
| 最高温度 | 16 |
| 最低温度 | 12 |
将这些数值代入公式:
* T max = 16
* T min = 12
* T base = 10
计算结果为:
![GDD = 16 + 12 除以 2然后减去 10结果为 4](../../../../../translated_images/zh-CN/gdd-calculation-corn.64a58b7a7afcd0dfd46ff733996d939f17f4f3feac9f0d1c632be3523e51ebd9.png)
当天玉米获得了 4 GDD。假设一种需要 800 GDD 才能成熟的玉米品种,还需要 796 GDD 才能达到成熟。
✅ 做一些研究。对于您花园、学校或当地公园中的植物,看看是否能找到它们达到成熟或产出作物所需的 GDD 数量。
## 使用温度传感器数据计算 GDD
植物的生长并不是固定日期的——例如,您不能种下一颗种子并准确知道它会在 100 天后结果。农民通常会大致估算植物的生长时间,然后每天检查作物是否成熟。
在大型农场,这会对劳动力产生巨大影响,并且农民可能会错过意外提前成熟的作物。通过测量温度,农民可以计算植物获得的 GDD从而只需在接近预期成熟时检查作物。
通过使用物联网设备收集温度数据,农民可以在植物接近成熟时自动收到通知。一个典型的架构是物联网设备测量温度,然后通过类似 MQTT 的方式将这些遥测数据发布到互联网。服务器代码监听这些数据并将其保存到某处,例如数据库。这意味着数据可以稍后进行分析,例如夜间作业计算当天的 GDD总结每种作物到目前为止的 GDD并在植物接近成熟时发出警报。
![遥测数据发送到服务器并保存到数据库](../../../../../translated_images/zh-CN/save-telemetry-database.ddc9c6bea0c5ba39.webp)
服务器代码还可以增强数据,例如添加额外信息。物联网设备可以发布一个标识符来指示设备的身份,服务器代码可以使用此标识符查找设备的位置以及它正在监测的作物。它还可以添加基本数据,例如当前时间,因为某些物联网设备没有必要的硬件来准确跟踪时间,或者需要额外的代码通过互联网读取当前时间。
✅ 您认为为什么不同的田地可能会有不同的温度?
### 任务 - 发布温度信息
按照相关指南通过您的物联网设备使用 MQTT 发布温度数据,以便稍后进行分析:
* [Arduino - Wio Terminal](wio-terminal-temp-publish.md)
* [单板计算机 - Raspberry Pi/虚拟物联网设备](single-board-computer-temp-publish.md)
### 任务 - 捕获并存储温度信息
一旦物联网设备开始发布遥测数据,服务器代码可以编写订阅这些数据并存储它们。与将数据保存到数据库不同,服务器代码会将数据保存到逗号分隔值 (CSV) 文件中。CSV 文件以文本形式存储数据,每行记录一个值,每个值之间用逗号分隔。它是一种方便、可读性强且广泛支持的文件存储方式。
CSV 文件将有两列——*日期* 和 *温度*。*日期* 列设置为服务器接收到消息的当前日期和时间,*温度* 列来自遥测消息。
1. 重复第 4 课中的步骤,创建订阅遥测数据的服务器代码。您无需添加发布命令的代码。
这些步骤包括:
* 配置并激活 Python 虚拟环境
* 安装 paho-mqtt pip 包
* 编写代码以监听发布到遥测主题的 MQTT 消息
> ⚠️ 如果需要,可以参考 [第 4 课中创建 Python 应用程序以接收遥测数据的说明](../../../1-getting-started/lessons/4-connect-internet/README.md#receive-telemetry-from-the-mqtt-broker)。
将此项目的文件夹命名为 `temperature-sensor-server`
1. 确保 `client_name` 反映此项目:
```cpp
client_name = id + 'temperature_sensor_server'
```
1. 在文件顶部的现有导入代码下添加以下导入:
```python
from os import path
import csv
from datetime import datetime
```
这将导入一个用于读取文件的库、一个与 CSV 文件交互的库以及一个帮助处理日期和时间的库。
1. 在 `handle_telemetry` 函数之前添加以下代码:
```python
temperature_file_name = 'temperature.csv'
fieldnames = ['date', 'temperature']
if not path.exists(temperature_file_name):
with open(temperature_file_name, mode='w') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
writer.writeheader()
```
此代码声明了一些常量,用于指定要写入的文件名和 CSV 文件的列标题名称。CSV 文件的第一行通常包含用逗号分隔的列标题。
然后代码检查 CSV 文件是否已经存在。如果不存在,则创建一个包含列标题的第一行。
1. 在 `handle_telemetry` 函数末尾添加以下代码:
```python
with open(temperature_file_name, mode='a') as temperature_file:
temperature_writer = csv.DictWriter(temperature_file, fieldnames=fieldnames)
temperature_writer.writerow({'date' : datetime.now().astimezone().replace(microsecond=0).isoformat(), 'temperature' : payload['temperature']})
```
此代码打开CSV文件然后在末尾添加一行新数据。新行包含当前日期和时间格式为易于阅读的形式以及从IoT设备接收到的温度数据。数据以[ISO 8601格式](https://wikipedia.org/wiki/ISO_8601)存储,包含时区信息,但不包括微秒。
1. 像之前一样运行此代码确保您的IoT设备正在发送数据。一个名为`temperature.csv`的CSV文件将在同一文件夹中创建。如果查看该文件您会看到日期/时间和温度测量值:
```output
date,temperature
2021-04-19T17:21:36-07:00,25
2021-04-19T17:31:36-07:00,24
2021-04-19T17:41:36-07:00,25
```
1. 运行此代码一段时间以捕获数据。理想情况下您应该运行一整天以收集足够的数据用于GDD计算。
> 💁 如果您使用的是虚拟IoT设备请勾选随机选项框并设置一个范围以避免每次返回的温度值都相同。
![勾选随机选项框并设置范围](../../../../../translated_images/zh-CN/select-the-random-checkbox-and-set-a-range.32cf4bc7c12e797f.webp)
> 💁 如果您想运行一整天,那么您需要确保运行服务器代码的计算机不会进入睡眠状态,可以通过更改电源设置或运行类似[这个保持系统活跃的Python脚本](https://github.com/jaqsparow/keep-system-active)来实现。
> 💁 您可以在[code-server/temperature-sensor-server](../../../../../2-farm/lessons/1-predict-plant-growth/code-server/temperature-sensor-server)文件夹中找到此代码。
### 任务 - 使用存储的数据计算GDD
一旦服务器捕获了温度数据就可以计算植物的GDD。
手动执行此操作的步骤如下:
1. 找到植物的基准温度。例如对于草莓基准温度是10°C。
1. 从`temperature.csv`中找到当天的最高和最低温度。
1. 使用之前给出的GDD计算公式计算GDD。
例如如果当天的最高温度是25°C最低温度是12°C
![GDD = 25 + 12 除以2然后从结果中减去10得到8.5](../../../../../translated_images/zh-CN/gdd-calculation-strawberries.59f57db94b22adb8ff6efb951ace33af104a1c6ccca3ffb0f8169c14cb160c90.png)
* 25 + 12 = 37
* 37 / 2 = 18.5
* 18.5 - 10 = 8.5
因此,草莓获得了**8.5** GDD。草莓需要大约250 GDD才能结果所以还需要一段时间。
---
## 🚀 挑战
植物的生长不仅需要热量,还需要其他条件。还有哪些因素是必需的?
对于这些因素寻找是否有传感器可以测量它们。是否有执行器可以控制这些水平您会如何组合一个或多个IoT设备来优化植物生长
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/10)
## 复习与自学
* 在[数字农业维基百科页面](https://wikipedia.org/wiki/Digital_agriculture)上阅读更多关于数字农业的内容。还可以在[精准农业维基百科页面](https://wikipedia.org/wiki/Precision_agriculture)上了解更多关于精准农业的内容。
* 完整的生长度日计算比这里给出的简化公式更复杂。阅读更多关于更复杂的公式以及如何处理低于基准温度的情况,请参阅[生长度日维基百科页面](https://wikipedia.org/wiki/Growing_degree-day)。
* 如果我们仍然使用相同的耕作方法,未来可能会面临粮食短缺问题。了解更多关于高科技农业技术的信息,请观看这个[未来高科技农场视频](https://www.youtube.com/watch?v=KIEOuKD9KX8)。
## 作业
[使用Jupyter Notebook可视化GDD数据](assignment.md)
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,46 @@
# 使用 Jupyter Notebook 可视化 GDD 数据
## 说明
在本课中,你使用 IoT 传感器收集了 GDD 数据。为了获得高质量的 GDD 数据,你需要收集多天的数据。为了帮助可视化温度数据并计算 GDD你可以使用像 [Jupyter Notebooks](https://jupyter.org) 这样的工具来分析数据。
首先,收集几天的数据。你需要确保服务器代码在 IoT 设备运行时始终保持运行状态,可以通过调整电源管理设置或运行类似于 [这个保持系统活跃的 Python 脚本](https://github.com/jaqsparow/keep-system-active) 来实现。
一旦你有了温度数据,就可以使用此仓库中的 Jupyter Notebook 来可视化数据并计算 GDD。Jupyter Notebook 将代码和说明混合在称为 *单元格* 的块中,通常是 Python 代码。你可以阅读说明,然后逐块运行代码块。你还可以编辑代码。例如,在这个 Notebook 中,你可以编辑用于计算植物 GDD 的基准温度。
1. 创建一个名为 `gdd-calculation` 的文件夹
1. 下载 [gdd.ipynb](../../../../../2-farm/lessons/1-predict-plant-growth/code-notebook/gdd.ipynb) 文件并将其复制到 `gdd-calculation` 文件夹中。
1. 复制由 MQTT 服务器创建的 `temperature.csv` 文件
1. 在 `gdd-calculation` 文件夹中创建一个新的 Python 虚拟环境。
1. 安装一些用于 Jupyter Notebook 的 pip 包,以及管理和绘制数据所需的库:
```sh
pip install --upgrade pip
pip install pandas
pip install matplotlib
pip install jupyter
```
1. 在 Jupyter 中运行 Notebook
```sh
jupyter notebook gdd.ipynb
```
Jupyter 将启动并在浏览器中打开 Notebook。按照 Notebook 中的说明操作可视化测量的温度并计算生长度日GDD
![Jupyter Notebook 示例](../../../../../translated_images/zh-CN/gdd-jupyter-notebook.c5b52cf21094f158a61f47f455490fd95f1729777ff90861a4521820bf354cdc.png)
## 评分标准
| 标准 | 优秀 | 合格 | 需要改进 |
| ---- | ---- | ---- | -------- |
| 数据采集 | 至少采集 2 天完整数据 | 至少采集 1 天完整数据 | 采集了一些数据 |
| GDD 计算 | 成功运行 Notebook 并计算 GDD | 成功运行 Notebook | 无法运行 Notebook |
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,167 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 生长积温天数\n",
"\n",
"此笔记本加载保存在 CSV 文件中的温度数据,并进行分析。它绘制温度图,显示每天的最高和最低值,并计算 GDD。\n",
"\n",
"使用此笔记本的方法:\n",
"\n",
"* 将 `temperature.csv` 文件复制到与此笔记本相同的文件夹中\n",
"* 使用上方的 **▶︎ 运行** 按钮运行所有单元格。这将运行选定的单元格,然后移动到下一个单元格。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"在下面的单元格中,将 `base_temperature` 设置为植物的基准温度。\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"base_temperature = 10"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"现在需要使用pandas加载CSV文件\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# Read the temperature CSV file\n",
"df = pd.read_csv('temperature.csv')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(20, 10))\n",
"plt.plot(df['date'], df['temperature'])\n",
"plt.xticks(rotation='vertical');"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"一旦数据被读取,可以按`date`列进行分组,并提取每个日期的最低和最高温度。\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Convert datetimes to pure dates so we can group by the date\n",
"df['date'] = pd.to_datetime(df['date']).dt.date\n",
"\n",
"# Group the data by date so it can be analyzed by date\n",
"data_by_date = df.groupby('date')\n",
"\n",
"# Get the minimum and maximum temperatures for each date\n",
"min_by_date = data_by_date.min()\n",
"max_by_date = data_by_date.max()\n",
"\n",
"# Join the min and max temperatures into one dataframe and flatten it\n",
"min_max_by_date = min_by_date.join(max_by_date, on='date', lsuffix='_min', rsuffix='_max')\n",
"min_max_by_date = min_max_by_date.reset_index()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"GDD可以使用标准GDD公式计算\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def calculate_gdd(row):\n",
" return ((row['temperature_max'] + row['temperature_min']) / 2) - base_temperature\n",
"\n",
"# Calculate the GDD for each row\n",
"min_max_by_date['gdd'] = min_max_by_date.apply (lambda row: calculate_gdd(row), axis=1)\n",
"\n",
"# Print the results\n",
"print(min_max_by_date[['date', 'gdd']].to_string(index=False))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**免责声明** \n本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.1"
},
"metadata": {
"interpreter": {
"hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49"
}
},
"coopTranslator": {
"original_hash": "8fcf954f6042f0bf3601a2c836a09574",
"translation_date": "2025-08-26T15:57:50+00:00",
"source_file": "2-farm/lessons/1-predict-plant-growth/code-notebook/gdd.ipynb",
"language_code": "zh"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

@ -0,0 +1,114 @@
# 测量温度 - 树莓派
在本节课程中,您将为树莓派添加一个温度传感器。
## 硬件
您将使用的传感器是 [DHT11 湿度和温度传感器](https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-DHT11.html)它将两个传感器集成在一个模块中。这是一款相当流行的传感器许多市售传感器都将温度、湿度有时还包括大气压力结合在一起。温度传感器部分是一个负温度系数NTC热敏电阻这是一种电阻随温度升高而减小的热敏电阻。
这是一个数字传感器,因此它内置了一个 ADC模数转换器可以生成包含温度和湿度数据的数字信号供微控制器读取。
### 连接温度传感器
Grove 温度传感器可以连接到树莓派。
#### 任务
连接温度传感器
![一个 Grove 温度传感器](../../../../../translated_images/zh-CN/grove-dht11.07f8eafceee170043efbb53e1d15722bd4e00fbaa9ff74290b57e9f66eb82c17.png)
1. 将 Grove 电缆的一端插入湿度和温度传感器上的插座。它只能以一种方式插入。
1. 在树莓派断电的情况下,将 Grove 电缆的另一端连接到安装在树莓派上的 Grove Base Hat 上标记为 **D5** 的数字插座。这个插座位于 GPIO 引脚旁边的一排插座中,从左数第二个。
![Grove 温度传感器连接到 A0 插座](../../../../../translated_images/zh-CN/pi-temperature-sensor.3ff82fff672c8e56.webp)
## 编程温度传感器
现在可以为设备编程以使用连接的温度传感器。
### 任务
为设备编程。
1. 启动树莓派并等待其启动完成。
1. 启动 VS Code可以直接在树莓派上运行也可以通过 Remote SSH 扩展连接。
> ⚠️ 如果需要,可以参考[第 1 课中设置和启动 VS Code 的说明](../../../1-getting-started/lessons/1-introduction-to-iot/pi.md)。
1. 在终端中,在 `pi` 用户的主目录下创建一个名为 `temperature-sensor` 的新文件夹。在该文件夹中创建一个名为 `app.py` 的文件:
```sh
mkdir temperature-sensor
cd temperature-sensor
touch app.py
```
1. 在 VS Code 中打开这个文件夹。
1. 要使用温度和湿度传感器,需要安装一个额外的 Pip 包。在 VS Code 的终端中运行以下命令,在树莓派上安装该 Pip 包:
```sh
pip3 install seeed-python-dht
```
1. 将以下代码添加到 `app.py` 文件中以导入所需的库:
```python
import time
from seeed_dht import DHT
```
`from seeed_dht import DHT` 语句从 `seeed_dht` 模块中导入 `DHT` 传感器类,用于与 Grove 温度传感器交互。
1. 在上述代码之后添加以下代码,以创建一个管理温度传感器的类实例:
```python
sensor = DHT("11", 5)
```
这声明了一个 `DHT` 类的实例,用于管理**数字湿度和温度传感器**。第一个参数告诉代码使用的是 *DHT11* 传感器——您使用的库还支持该传感器的其他变体。第二个参数告诉代码传感器连接到 Grove Base Hat 的数字端口 `D5`
> ✅ 请记住,所有插座都有唯一的引脚编号。引脚 0、2、4 和 6 是模拟引脚,引脚 5、16、18、22、24 和 26 是数字引脚。
1. 在上述代码之后添加一个无限循环,用于轮询温度传感器的值并将其打印到控制台:
```python
while True:
_, temp = sensor.read()
print(f'Temperature {temp}°C')
```
调用 `sensor.read()` 会返回一个包含湿度和温度的元组。您只需要温度值,因此可以忽略湿度值。然后将温度值打印到控制台。
1. 在循环末尾添加一个 10 秒的小延迟,因为不需要连续检查温度水平。延迟可以减少设备的功耗。
```python
time.sleep(10)
```
1. 在 VS Code 的终端中运行以下命令以运行您的 Python 应用程序:
```sh
python3 app.py
```
您应该会看到温度值输出到控制台。可以通过用手指按住传感器或使用风扇来加热传感器,观察值的变化:
```output
pi@raspberrypi:~/temperature-sensor $ python3 app.py
Temperature 26°C
Temperature 26°C
Temperature 28°C
Temperature 30°C
Temperature 32°C
```
> 💁 您可以在 [code-temperature/pi](../../../../../2-farm/lessons/1-predict-plant-growth/code-temperature/pi) 文件夹中找到此代码。
😀 您的温度传感器程序运行成功!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,60 @@
# 发布温度 - 虚拟物联网硬件和树莓派
在本课的这一部分中,您将通过 MQTT 发布树莓派或虚拟物联网设备检测到的温度值,以便稍后用于计算 GDD。
## 发布温度
一旦读取了温度值,就可以通过 MQTT 发布到某些“服务器”代码,这些代码将读取这些值并存储起来,以便用于 GDD 计算。
### 任务 - 发布温度
编程设备以发布温度数据。
1. 如果尚未打开,请打开 `temperature-sensor` 应用项目。
1. 重复您在第 4 课中完成的步骤以连接到 MQTT 并发送遥测数据,您将使用相同的公共 Mosquitto broker。
这些步骤包括:
- 添加 MQTT pip 包
- 添加连接到 MQTT broker 的代码
- 添加发布遥测数据的代码
> ⚠️ 如果需要,请参考第 4 课中的[连接到 MQTT 的说明](../../../1-getting-started/lessons/4-connect-internet/single-board-computer-mqtt.md)和[发送遥测数据的说明](../../../1-getting-started/lessons/4-connect-internet/single-board-computer-telemetry.md)。
1. 确保 `client_name` 反映此项目的名称:
```python
client_name = id + 'temperature_sensor_client'
```
1. 对于遥测数据,不再发送光照值,而是发送从 DHT 传感器读取的温度值,并将其作为 JSON 文档中的 `temperature` 属性:
```python
_, temp = sensor.read()
telemetry = json.dumps({'temperature' : temp})
```
1. 温度值不需要频繁读取——在短时间内不会有太大变化,因此将 `time.sleep` 设置为 10 分钟:
```cpp
time.sleep(10 * 60);
```
> 💁 `sleep` 函数以秒为单位接收时间为了更容易理解时间值是通过计算得出的。1 分钟有 60 秒,因此 10 x (1 分钟的 60 秒) 得到 10 分钟的延迟。
1. 以与之前任务代码相同的方式运行代码。如果您使用的是虚拟物联网设备,请确保 CounterFit 应用正在运行,并且湿度和温度传感器已在正确的引脚上创建。
```output
pi@raspberrypi:~/temperature-sensor $ python3 app.py
MQTT connected!
Sending telemetry {"temperature": 25}
Sending telemetry {"temperature": 25}
```
> 💁 您可以在 [code-publish-temperature/virtual-device](../../../../../2-farm/lessons/1-predict-plant-growth/code-publish-temperature/virtual-device) 文件夹或 [code-publish-temperature/pi](../../../../../2-farm/lessons/1-predict-plant-growth/code-publish-temperature/pi) 文件夹中找到此代码。
😀 您已成功将设备的温度作为遥测数据发布。
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,143 @@
# 测量温度 - 虚拟物联网硬件
在本节课程中,您将为虚拟物联网设备添加一个温度传感器。
## 虚拟硬件
虚拟物联网设备将使用模拟的 Grove 数字湿度和温度传感器。这使得本实验与使用带有物理 Grove DHT11 传感器的树莓派保持一致。
该传感器结合了一个**温度传感器**和一个**湿度传感器**,但在本实验中,您只需关注温度传感器部分。在物理物联网设备中,温度传感器通常是一个[热敏电阻](https://wikipedia.org/wiki/Thermistor),通过感知电阻随温度变化而变化来测量温度。温度传感器通常是数字传感器,它会在内部将测得的电阻转换为摄氏度(或开尔文、华氏度)的温度。
### 将传感器添加到 CounterFit
要使用虚拟湿度和温度传感器,您需要将这两个传感器添加到 CounterFit 应用中。
#### 任务 - 将传感器添加到 CounterFit
将湿度和温度传感器添加到 CounterFit 应用中。
1. 在您的计算机上创建一个名为 `temperature-sensor` 的文件夹,并在其中创建一个名为 `app.py` 的单文件 Python 应用,同时创建一个 Python 虚拟环境,并添加 CounterFit 的 pip 包。
> ⚠️ 如果需要,您可以参考[第 1 课中创建和设置 CounterFit Python 项目的说明](../../../1-getting-started/lessons/1-introduction-to-iot/virtual-device.md)。
1. 安装一个额外的 Pip 包,用于安装 DHT11 传感器的 CounterFit shim。确保您是在激活虚拟环境的终端中安装的。
```sh
pip install counterfit-shims-seeed-python-dht
```
1. 确保 CounterFit Web 应用正在运行。
1. 创建一个湿度传感器:
1. 在 *Sensors* 面板的 *Create sensor* 框中,点击 *Sensor type* 下拉框并选择 *Humidity*
1. 将 *Units* 保持为 *Percentage*
1. 确保 *Pin* 设置为 *5*
1. 点击 **Add** 按钮,在 Pin 5 上创建湿度传感器。
![湿度传感器设置](../../../../../translated_images/zh-CN/counterfit-create-humidity-sensor.2750e27b6f30e09cf4e22101defd5252710717620816ab41ba688f91f757c49a.png)
湿度传感器将被创建并显示在传感器列表中。
![湿度传感器已创建](../../../../../translated_images/zh-CN/counterfit-humidity-sensor.7b12f7f339e430cb26c8211d2dba4ef75261b353a01da0932698b5bebd693f27.png)
1. 创建一个温度传感器:
1. 在 *Sensors* 面板的 *Create sensor* 框中,点击 *Sensor type* 下拉框并选择 *Temperature*
1. 将 *Units* 保持为 *Celsius*
1. 确保 *Pin* 设置为 *6*
1. 点击 **Add** 按钮,在 Pin 6 上创建温度传感器。
![温度传感器设置](../../../../../translated_images/zh-CN/counterfit-create-temperature-sensor.199350ed34f7343d79dccbe95eaf6c11d2121f03d1c35ab9613b330c23f39b29.png)
温度传感器将被创建并显示在传感器列表中。
![温度传感器已创建](../../../../../translated_images/zh-CN/counterfit-temperature-sensor.f0560236c96a9016bafce7f6f792476fe3367bc6941a1f7d5811d144d4bcbfff.png)
## 编写温度传感器应用程序
现在可以使用 CounterFit 传感器编写温度传感器应用程序。
### 任务 - 编写温度传感器应用程序
编写温度传感器应用程序。
1. 确保在 VS Code 中打开了 `temperature-sensor` 应用。
1. 打开 `app.py` 文件。
1. 在 `app.py` 文件顶部添加以下代码,将应用程序连接到 CounterFit
```python
from counterfit_connection import CounterFitConnection
CounterFitConnection.init('127.0.0.1', 5000)
```
1. 在 `app.py` 文件中添加以下代码以导入所需的库:
```python
import time
from counterfit_shims_seeed_python_dht import DHT
```
`from seeed_dht import DHT` 语句导入了 `DHT` 传感器类,用于通过 `counterfit_shims_seeed_python_dht` 模块的 shim 与虚拟 Grove 温度传感器交互。
1. 在上述代码之后添加以下代码,以创建管理虚拟湿度和温度传感器的类实例:
```python
sensor = DHT("11", 5)
```
这声明了一个 `DHT` 类的实例,用于管理虚拟的**数字湿度和温度传感器**。第一个参数告诉代码使用的是虚拟 *DHT11* 传感器。第二个参数告诉代码传感器连接到端口 `5`
> 💁 CounterFit 通过连接两个传感器来模拟这个组合的湿度和温度传感器,一个湿度传感器连接到创建 `DHT` 类时给定的引脚,另一个温度传感器运行在下一个引脚上。如果湿度传感器在引脚 5 上shim 会期望温度传感器在引脚 6 上。
1. 在上述代码之后添加一个无限循环,以轮询温度传感器的值并将其打印到控制台:
```python
while True:
_, temp = sensor.read()
print(f'Temperature {temp}°C')
```
调用 `sensor.read()` 会返回一个包含湿度和温度的元组。您只需要温度值,因此可以忽略湿度值。然后将温度值打印到控制台。
1. 在循环末尾添加一个 10 秒的小延迟,因为温度水平不需要连续检查。延迟可以减少设备的功耗。
```python
time.sleep(10)
```
1. 在 VS Code 的终端中,激活虚拟环境后运行以下命令以运行您的 Python 应用程序:
```sh
python app.py
```
1. 在 CounterFit 应用中更改温度传感器的值,以便应用读取该值。您可以通过以下两种方式之一进行更改:
* 在温度传感器的 *Value* 框中输入一个数字,然后点击 **Set** 按钮。您输入的数字将是传感器返回的值。
* 勾选 *Random* 复选框,并输入 *Min**Max* 值,然后点击 **Set** 按钮。每次传感器读取值时,它将读取一个介于 *Min**Max* 之间的随机数。
您应该会在控制台中看到您设置的值。更改 *Value**Random* 设置以查看值的变化。
```output
(.venv) ➜ temperature-sensor python app.py
Temperature 28.25°C
Temperature 30.71°C
Temperature 25.17°C
```
> 💁 您可以在 [code-temperature/virtual-device](../../../../../2-farm/lessons/1-predict-plant-growth/code-temperature/virtual-device) 文件夹中找到此代码。
😀 您的温度传感器程序运行成功!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,71 @@
# 发布温度 - Wio Terminal
在本节课程中,您将通过 MQTT 发布 Wio Terminal 检测到的温度值,以便后续用于计算 GDD。
## 发布温度
一旦读取到温度值,就可以通过 MQTT 发布到某些“服务器”代码,这些代码将读取这些值并存储起来,以便用于 GDD 计算。微控制器不会直接从互联网读取时间,也不会默认使用实时时钟来跟踪时间,设备需要进行编程才能实现这一功能,前提是它具备必要的硬件。
为了简化本课程的内容,传感器数据不会附带时间信息,而是由服务器代码在接收到消息后添加时间。
### 任务
编程设备以发布温度数据。
1. 打开 `temperature-sensor` Wio Terminal 项目
1. 重复您在第 4 课中完成的步骤,连接到 MQTT 并发送遥测数据。您将使用相同的公共 Mosquitto broker。
具体步骤如下:
- 将 Seeed WiFi 和 MQTT 库添加到 `.ini` 文件
- 添加配置文件和连接 WiFi 的代码
- 添加连接到 MQTT broker 的代码
- 添加发布遥测数据的代码
> ⚠️ 如果需要,请参考 [连接到 MQTT 的说明](../../../1-getting-started/lessons/4-connect-internet/wio-terminal-mqtt.md) 和 [发送遥测数据的说明](../../../1-getting-started/lessons/4-connect-internet/wio-terminal-telemetry.md)(第 4 课)。
1. 确保 `config.h` 头文件中的 `CLIENT_NAME` 反映了当前项目:
```cpp
const string CLIENT_NAME = ID + "temperature_sensor_client";
```
1. 对于遥测数据,不再发送光照值,而是发送从 DHT 传感器读取的温度值,并将其作为 JSON 文档中的 `temperature` 属性。为此,请修改 `main.cpp` 中的 `loop` 函数:
```cpp
float temp_hum_val[2] = {0};
dht.readTempAndHumidity(temp_hum_val);
DynamicJsonDocument doc(1024);
doc["temperature"] = temp_hum_val[1];
```
1. 温度值不需要频繁读取——在短时间内它不会有太大变化,因此将 `loop` 函数中的 `delay` 设置为 10 分钟:
```cpp
delay(10 * 60 * 1000);
```
> 💁 `delay` 函数的时间单位是毫秒为了便于阅读时间以计算结果的形式传递。1,000 毫秒为 1 秒60 秒为 1 分钟,因此 10 x (60 秒/分钟) x (1000 毫秒/秒) 得到 10 分钟的延迟。
1. 将代码上传到您的 Wio Terminal并使用串口监视器查看温度值是否已发送到 MQTT broker。
```output
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at http://bit.ly/pio-monitor-filters
--- Miniterm on /dev/cu.usbmodem1201 9600,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
Connecting to WiFi..
Connected!
Attempting MQTT connection...connected
Sending telemetry {"temperature":25}
Sending telemetry {"temperature":25}
```
> 💁 您可以在 [code-publish-temperature/wio-terminal](../../../../../2-farm/lessons/1-predict-plant-growth/code-publish-temperature/wio-terminal) 文件夹中找到此代码。
😀 您已成功将设备的温度值作为遥测数据发布。
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,132 @@
# 测量温度 - Wio Terminal
在本节课程中,您将为 Wio Terminal 添加一个温度传感器,并从中读取温度值。
## 硬件
Wio Terminal 需要一个温度传感器。
您将使用的传感器是 [DHT11 湿度和温度传感器](https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-DHT11.html)它将两个传感器集成在一个封装中。这是一款非常流行的传感器许多商用传感器都结合了温度、湿度有时还包括大气压力。温度传感器组件是一个负温度系数NTC热敏电阻这是一种电阻随温度升高而减小的热敏电阻。
这是一个数字传感器,因此它内置了 ADC模数转换器可以生成包含温度和湿度数据的数字信号供微控制器读取。
### 连接温度传感器
Grove 温度传感器可以连接到 Wio Terminal 的数字端口。
#### 任务 - 连接温度传感器
连接温度传感器。
![一个 Grove 温度传感器](../../../../../translated_images/zh-CN/grove-dht11.07f8eafceee170043efbb53e1d15722bd4e00fbaa9ff74290b57e9f66eb82c17.png)
1. 将 Grove 电缆的一端插入湿度和温度传感器上的插座。电缆只能以一种方向插入。
1. 在 Wio Terminal 未连接到计算机或其他电源的情况下,将 Grove 电缆的另一端连接到 Wio Terminal 屏幕右侧的 Grove 插座。这是距离电源按钮最远的插座。
![Grove 温度传感器连接到右侧插座](../../../../../translated_images/zh-CN/wio-temperature-sensor.2934928f38c7f79a.webp)
## 编程温度传感器
现在可以为 Wio Terminal 编程以使用连接的温度传感器。
### 任务 - 编程温度传感器
为设备编程。
1. 使用 PlatformIO 创建一个全新的 Wio Terminal 项目。将此项目命名为 `temperature-sensor`。在 `setup` 函数中添加代码以配置串口。
> ⚠️ 如果需要,可以参考 [项目 1第 1 课中创建 PlatformIO 项目的说明](../../../1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md#create-a-platformio-project)。
1. 在项目的 `platformio.ini` 文件中添加 Seeed Grove 湿度和温度传感器库的库依赖项:
```ini
lib_deps =
seeed-studio/Grove Temperature And Humidity Sensor @ 1.0.1
```
> ⚠️ 如果需要,可以参考 [项目 1第 4 课中为 PlatformIO 项目添加库的说明](../../../1-getting-started/lessons/4-connect-internet/wio-terminal-mqtt.md#install-the-wifi-and-mqtt-arduino-libraries)。
1. 在文件顶部现有的 `#include <Arduino.h>` 下面添加以下 `#include` 指令:
```cpp
#include <DHT.h>
#include <SPI.h>
```
这会导入与传感器交互所需的文件。`DHT.h` 头文件包含传感器本身的代码,而添加 `SPI.h` 头文件确保在应用程序编译时链接与传感器通信所需的代码。
1. 在 `setup` 函数之前,声明 DHT 传感器:
```cpp
DHT dht(D0, DHT11);
```
这声明了一个 `DHT` 类的实例,用于管理 **D**igital **H**umidity 和 **T**emperature 传感器。该传感器连接到 Wio Terminal 的 `D0` 端口,即右侧的 Grove 插座。第二个参数告诉代码使用的是 *DHT11* 传感器——您使用的库支持该传感器的其他变体。
1. 在 `setup` 函数中,添加代码以设置串行连接:
```cpp
void setup()
{
Serial.begin(9600);
while (!Serial)
; // Wait for Serial to be ready
delay(1000);
}
```
1. 在 `setup` 函数的最后一个 `delay` 之后,添加调用以启动 DHT 传感器:
```cpp
dht.begin();
```
1. 在 `loop` 函数中,添加代码以调用传感器并将温度打印到串口:
```cpp
void loop()
{
float temp_hum_val[2] = {0};
dht.readTempAndHumidity(temp_hum_val);
Serial.print("Temperature: ");
Serial.print(temp_hum_val[1]);
Serial.println ("°C");
delay(10000);
}
```
此代码声明了一个包含两个浮点数的空数组,并将其传递给 `DHT` 实例上的 `readTempAndHumidity` 调用。此调用会将数组填充为两个值——湿度存储在数组的第 0 项(请记住,在 C++ 中数组是从 0 开始的,因此第 0 项是数组的“第一”项),温度存储在第 1 项。
温度从数组的第 1 项读取,并打印到串口。
> 🇺🇸 温度以摄氏度读取。对于美国用户,要将摄氏度转换为华氏度,请将读取的摄氏度值除以 5然后乘以 9再加上 32。例如20°C 的温度读取值转换为 ((20/5)*9) + 32 = 68°F。
1. 构建并上传代码到 Wio Terminal。
> ⚠️ 如果需要,可以参考 [项目 1第 1 课中创建 PlatformIO 项目的说明](../../../1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md#write-the-hello-world-app)。
1. 上传完成后,您可以使用串口监视器监控温度:
```output
> Executing task: platformio device monitor <
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at http://bit.ly/pio-monitor-filters
--- Miniterm on /dev/cu.usbmodem1201 9600,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
Temperature: 25.00°C
Temperature: 25.00°C
Temperature: 25.00°C
Temperature: 24.00°C
```
> 💁 您可以在 [code-temperature/wio-terminal](../../../../../2-farm/lessons/1-predict-plant-growth/code-temperature/wio-terminal) 文件夹中找到此代码。
😀 您的温度传感器程序运行成功!
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,158 @@
C读作 *I-squared-C*)是一种多控制器、多外围设备协议,任何连接的设备都可以作为控制器或外围设备,通过 I²C 总线(用于传输数据的通信系统名称)进行通信。数据以地址包的形式发送,每个包包含目标设备的地址。
> 💁 这种模型过去被称为主/从master/slave但由于其与奴隶制的关联这种术语正在被弃用。[开源硬件协会已采用控制器/外围设备的术语](https://www.oshwa.org/a-resolution-to-redefine-spi-signal-names/),但您可能仍会看到旧术语的引用。
设备在连接到 I²C 总线时会有一个地址通常硬编码在设备上。例如Seeed 的每种 Grove 传感器都有相同的地址,因此所有光传感器都有相同的地址,所有按钮的地址与光传感器的地址不同。一些设备可以通过改变跳线设置或焊接引脚来更改地址。
I²C 总线由两根主要的通信线和两根电源线组成:
| 线 | 名称 | 描述 |
| ---- | --------- | ----------- |
| SDA | 串行数据 | 这根线用于在设备之间发送数据。 |
| SCL | 串行时钟 | 这根线以控制器设定的速率发送时钟信号。 |
| VCC | 电压公共集电极 | 为设备提供电源。通过一个上拉电阻连接到 SDA 和 SCL 线,为它们提供电源,并在没有设备作为控制器时关闭信号。 |
| GND | 地线 | 为电路提供公共地线。 |
![I2C 总线连接了 3 个设备,这些设备共享 SDA 和 SCL 线以及一个公共地线](../../../../../translated_images/zh-CN/i2c.83da845dde02256bdd462dbe0d5145461416b74930571b89d1ae142841eeb584.png)
要发送数据,一个设备会发出启动条件,表明它准备好发送数据。它随后成为控制器。控制器接着发送它想要通信的设备地址,以及它是要读取还是写入数据。在数据传输完成后,控制器发送停止条件,表明它已完成。之后,另一个设备可以成为控制器并发送或接收数据。
2<sup>C 有速度限制,提供三种不同模式,运行在固定速度下。最快的模式是高速模式,最大速度为 3.4Mbps每秒兆比特但支持这种速度的设备非常少。例如Raspberry Pi 的速度限制在 400Kbps每秒千比特的快速模式。标准模式的运行速度为 100Kbps。
> 💁 如果您使用的是带有 Grove Base 扩展板的 Raspberry Pi 作为物联网硬件,您会在板上看到多个 I<sup>2</sup>C 插座,可以用来与 I<sup>2</sup>C 传感器通信。模拟 Grove 传感器也使用 I<sup>2</sup>C 和 ADC模数转换器将模拟值作为数字数据发送因此您使用的光传感器模拟了一个模拟引脚而值通过 I<sup>2</sup>C 发送,因为 Raspberry Pi 仅支持数字引脚。
### 通用异步收发器 (UART)
UART 涉及允许两个设备通信的物理电路。每个设备有两个通信引脚——发送Tx和接收Rx第一个设备的 Tx 引脚连接到第二个设备的 Rx 引脚,第二个设备的 Tx 引脚连接到第一个设备的 Rx 引脚。这使得数据可以双向传输。
* 设备 1 从其 Tx 引脚发送数据,设备 2 在其 Rx 引脚接收数据
* 设备 1 在其 Rx 引脚接收设备 2 从其 Tx 引脚发送的数据
![UART 中一个芯片的 Tx 引脚连接到另一个芯片的 Rx 引脚,反之亦然](../../../../../translated_images/zh-CN/uart.d0dbd3fb9e3728c6.webp)
> 🎓 数据以一位一位的方式发送,这被称为*串行*通信。大多数操作系统和微控制器都有*串行端口*,即可以发送和接收串行数据的连接,供您的代码使用。
UART 设备有一个 [波特率](https://wikipedia.org/wiki/Symbol_rate)(也称为符号率),即数据以每秒比特数发送和接收的速度。常见的波特率是 9,600这意味着每秒发送 9,600 比特0 和 1的数据。
UART 使用起始位和停止位——即它发送一个起始位来表示即将发送一个字节8 位)数据,然后在发送完 8 位后发送一个停止位。
UART 的速度取决于硬件,但即使是最快的实现也不会超过 6.5 Mbps每秒兆比特即每秒发送的 0 或 1 的百万比特)。
您可以通过 GPIO 引脚使用 UART——可以将一个引脚设置为 Tx另一个引脚设置为 Rx然后将它们连接到另一个设备。
> 💁 如果您使用的是带有 Grove Base 扩展板的 Raspberry Pi 作为物联网硬件,您会在板上看到一个 UART 插座,可以用来与使用 UART 协议的传感器通信。
### 串行外设接口 (SPI)
SPI 设计用于短距离通信,例如在微控制器上与存储设备(如闪存)通信。它基于控制器/外设模型,单个控制器(通常是物联网设备的处理器)与多个外设交互。控制器通过选择一个外设并发送或请求数据来控制一切。
> 💁 与 I<sup>2</sup>C 类似,控制器和外设的术语是最近的变化,因此您可能会看到仍在使用的旧术语。
SPI 控制器使用 3 根线,以及每个外设额外的一根线。外设使用 4 根线。这些线包括:
| 线 | 名称 | 描述 |
| ---- | --------- | ----------- |
| COPI | 控制器输出,外设输入 | 这根线用于将数据从控制器发送到外设。 |
| CIPO | 控制器输入,外设输出 | 这根线用于将数据从外设发送到控制器。 |
| SCLK | 串行时钟 | 这根线以控制器设置的速率发送时钟信号。 |
| CS | 芯片选择 | 控制器有多根线,每根线连接到相应外设的 CS 线。 |
![一个控制器和两个外设的 SPI](../../../../../translated_images/zh-CN/spi.297431d6f98b386b.webp)
CS 线用于一次激活一个外设,通过 COPI 和 CIPO 线进行通信。当控制器需要更换外设时,它会停用当前激活外设的 CS 线,然后激活连接到下一个外设的线。
SPI 是*全双工*的,这意味着控制器可以同时通过 COPI 和 CIPO 线从同一个外设发送和接收数据。SPI 使用 SCLK 线上的时钟信号来保持设备同步,因此不像直接通过 UART 发送数据那样需要起始位和停止位。
SPI 没有定义的速度限制,通常的实现可以每秒传输多个兆字节的数据。
物联网开发套件通常支持通过一些 GPIO 引脚使用 SPI。例如在 Raspberry Pi 上,您可以使用 GPIO 引脚 19、21、23、24 和 26 进行 SPI 通信。
### 无线
一些传感器可以通过标准无线协议通信例如蓝牙主要是低功耗蓝牙BLE、LoRaWAN**Lo**ng **Ra**nge 低功耗网络协议)或 WiFi。这些协议允许远程传感器无需物理连接到物联网设备。
一个例子是商业土壤湿度传感器。这些传感器会测量田地中的土壤湿度,然后通过 LoRaWAN 将数据发送到一个集线器设备,该设备会处理数据或通过互联网发送。这使得传感器可以远离管理数据的物联网设备,从而减少功耗和对大型 WiFi 网络或长电缆的需求。
BLE 在高级传感器中很受欢迎,例如手腕上的健身追踪器。这些设备结合了多个传感器,并通过 BLE 将传感器数据发送到物联网设备(如您的手机)。
✅ 您身上、家中或学校里是否有任何蓝牙传感器?这些可能包括温度传感器、占用传感器、设备追踪器和健身设备。
一种流行的商业设备连接方式是 Zigbee。Zigbee 使用 WiFi 在设备之间形成网状网络,每个设备尽可能多地连接到附近的设备,形成像蜘蛛网一样的大量连接。当一个设备想要向互联网发送消息时,它可以将消息发送到最近的设备,然后由这些设备转发,直到到达协调器并发送到互联网。
> 🐝 Zigbee 的名字来源于蜜蜂返回蜂巢后的摇摆舞。
## 测量土壤中的湿度水平
您可以使用土壤湿度传感器、物联网设备和一株室内植物或附近的一块土壤来测量土壤湿度水平。
### 任务 - 测量土壤湿度
按照相关指南,使用您的物联网设备测量土壤湿度:
* [Arduino - Wio Terminal](wio-terminal-soil-moisture.md)
* [单板计算机 - Raspberry Pi](pi-soil-moisture.md)
* [单板计算机 - 虚拟设备](virtual-device-soil-moisture.md)
## 传感器校准
传感器依赖于测量电气特性,例如电阻或电容。
> 🎓 电阻,以欧姆 (Ω) 为单位,是对电流通过某物的阻力。当电压施加到某种材料时,通过它的电流量取决于材料的电阻。您可以在 [电阻的维基百科页面](https://wikipedia.org/wiki/Electrical_resistance_and_conductance) 上阅读更多内容。
> 🎓 电容,以法拉 (F) 为单位,是组件或电路收集和存储电能的能力。您可以在 [电容的维基百科页面](https://wikipedia.org/wiki/Capacitance) 上阅读更多内容。
这些测量值并不总是有用的——想象一个温度传感器给您的测量值是 22.5KΩ!因此,测量值需要通过校准转换为有用的单位——即将测量值与测量的物理量匹配,以便将新测量值转换为正确的单位。
一些传感器是预校准的。例如,您在上一课中使用的温度传感器已经校准,可以返回以 °C 为单位的温度测量值。在工厂中,第一个传感器会暴露在一系列已知温度下,并测量电阻。这将用于构建一个计算公式,可以将以 Ω(电阻单位)为单位的测量值转换为 °C。
> 💁 计算电阻与温度的公式称为 [SteinhartHart 方程](https://wikipedia.org/wiki/SteinhartHart_equation)。
### 土壤湿度传感器校准
土壤湿度通过重量含水量或体积含水量来测量。
* 重量含水量是单位重量土壤中的水的重量,以每千克干土中的千克水表示
* 体积含水量是单位体积土壤中的水的体积,以每立方米干土中的立方米水表示
> 🇺🇸 对于美国人,由于单位的一致性,这些可以用磅代替千克或立方英尺代替立方米来测量。
土壤湿度传感器测量电阻或电容——这不仅随土壤湿度变化,还随土壤类型变化,因为土壤中的成分会改变其电气特性。理想情况下,传感器应进行校准——即从传感器获取读数并与通过更科学的方法获得的测量值进行比较。例如,实验室可以通过对特定田地的样本进行一年几次的测量来计算重量含水量,然后使用这些数据校准传感器,将传感器读数与重量含水量匹配。
![电压与土壤湿度含量的关系图](../../../../../translated_images/zh-CN/soil-moisture-to-voltage.df86d80cda158700.webp)
上图显示了如何校准传感器。通过对土壤样本进行电压测量,然后在实验室中通过比较湿重和干重(通过测量湿重,然后在烘箱中干燥并测量干重)来测量湿度。获取几个读数后,可以将其绘制在图表上,并拟合一条线。这条线可以用来将物联网设备获取的土壤湿度传感器读数转换为实际的土壤湿度测量值。
💁 对于电阻式土壤湿度传感器,电压随着土壤湿度的增加而增加。对于电容式土壤湿度传感器,电压随着土壤湿度的增加而减少,因此这些传感器的图表会向下倾斜,而不是向上。
![从图表中插值的土壤湿度值](../../../../../translated_images/zh-CN/soil-moisture-to-voltage-with-reading.681cb3e1f8b68caf.webp)
上图显示了土壤湿度传感器的电压读数,通过将其与图表上的线相交,可以计算出实际的土壤湿度。
这种方法意味着农民只需要为一个田地获取少量实验室测量值,然后可以使用物联网设备测量土壤湿度——大大加快了测量时间。
---
## 🚀 挑战
电阻式和电容式土壤湿度传感器有许多不同之处。这些差异是什么?哪种类型(如果有的话)最适合农民使用?在发展中国家和发达国家之间,这个答案是否会有所不同?
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/12)
## 复习与自学
阅读传感器和执行器使用的硬件和协议:
* [GPIO 维基百科页面](https://wikipedia.org/wiki/General-purpose_input/output)
* [UART 维基百科页面](https://wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter)
* [SPI 维基百科页面](https://wikipedia.org/wiki/Serial_Peripheral_Interface)
* [I<sup>2</sup>C 维基百科页面](https://wikipedia.org/wiki/I²C)
* [Zigbee 维基百科页面](https://wikipedia.org/wiki/Zigbee)
## 作业
[校准您的传感器](assignment.md)
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而产生的任何误解或误读不承担责任。

@ -0,0 +1,50 @@
# 校准您的传感器
## 操作指南
在本课中您收集了土壤湿度传感器的读数范围为0-1023。为了将这些读数转换为实际的土壤湿度值您需要校准传感器。您可以通过从土壤样本中获取读数然后计算这些样本的重力土壤湿度含量来完成校准。
您需要多次重复以下步骤,每次使用不同湿度的土壤来获取所需的读数。
1. 使用土壤湿度传感器进行一次读数,并记录该读数。
2. 取一份土壤样本并称重,记录该重量。
3. 将土壤烘干——最好的方法是将其放入110°C230°F的温暖烤箱中烘干几个小时。您也可以将其放在阳光下或一个温暖干燥的地方直到土壤完全干燥。干燥后的土壤应该是粉状且松散的。
> 💁 在实验室中为了获得最准确的结果您可以在烤箱中烘干48-72小时。如果您的学校有烘干设备可以尝试使用这些设备进行更长时间的烘干。烘干时间越长样本越干燥结果越准确。
4. 再次称重干燥后的土壤。
> 🔥 如果您是在烤箱中烘干的,请确保土壤已经冷却!
重力土壤湿度的计算公式为:
![土壤湿度百分比等于湿土重量减去干土重量除以干土重量再乘以100](../../../../../translated_images/zh-CN/gsm-calculation.6da38c6201eec14e7573bb2647aa18892883193553d23c9d77e5dc681522dfb2.png)
* W - 湿土的重量
* W - 干土的重量
例如假设您有一个土壤样本湿重为212克干重为197克。
![填入计算公式的示例](../../../../../translated_images/zh-CN/gsm-calculation-example.99f9803b4f29e97668e7c15412136c0c399ab12dbba0b89596fdae9d8aedb6fb.png)
* W = 212克
* W = 197克
* 212 - 197 = 15
* 15 / 197 = 0.076
* 0.076 * 100 = 7.6%
在这个例子中土壤的重力土壤湿度为7.6%。
当您至少有3个样本的读数后绘制一个土壤湿度百分比与土壤湿度传感器读数的图表并添加一条最佳拟合线。然后您可以通过从拟合线读取值来计算给定传感器读数的重力土壤湿度含量。
## 评分标准
| 标准 | 优秀 | 合格 | 需要改进 |
| ---- | ---- | ---- | -------- |
| 收集校准数据 | 至少采集3个校准样本 | 至少采集2个校准样本 | 至少采集1个校准样本 |
| 进行校准读数 | 成功绘制校准图表,并从传感器读数中转换为重力土壤湿度含量 | 成功绘制校准图表 | 无法绘制图表 |
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,97 @@
# 测量土壤湿度 - 树莓派
在本节课程中,您将为树莓派添加一个电容式土壤湿度传感器,并从中读取数据。
## 硬件
树莓派需要一个电容式土壤湿度传感器。
您将使用的传感器是[电容式土壤湿度传感器](https://www.seeedstudio.com/Grove-Capacitive-Moisture-Sensor-Corrosion-Resistant.html),它通过检测土壤的电容来测量湿度,这种特性会随着土壤湿度的变化而变化。随着土壤湿度的增加,电压会降低。
这是一个模拟传感器,因此使用模拟引脚,并通过树莓派上的 Grove Base Hat 中的 10 位 ADC 将电压转换为 1-1,023 的数字信号。然后通过树莓派的 GPIO 引脚以 I²C 的方式传输。
### 连接土壤湿度传感器
Grove 土壤湿度传感器可以连接到树莓派。
#### 任务 - 连接土壤湿度传感器
连接土壤湿度传感器。
![一个 Grove 土壤湿度传感器](../../../../../translated_images/zh-CN/grove-capacitive-soil-moisture-sensor.e7f0776cce30e78be5cc5a07839385fd6718857f31b5bf5ad3d0c73c83b2f0ef.png)
1. 将 Grove 电缆的一端插入土壤湿度传感器上的插座。它只能以一种方式插入。
1. 在树莓派断电的情况下,将 Grove 电缆的另一端连接到 Grove Base Hat 上标记为 **A0** 的模拟插座。这个插座位于 GPIO 引脚旁边的一排插座中,从右数第二个。
![Grove 土壤湿度传感器连接到 A0 插座](../../../../../translated_images/zh-CN/pi-soil-moisture-sensor.fdd7eb2393792cf6739cacf1985d9f55beda16d372f30d0b5a51d586f978a870.png)
1. 将土壤湿度传感器插入土壤中。传感器上有一条“最高位置线”——一条横跨传感器的白线。将传感器插入到这条线以下,但不要超过这条线。
![插入土壤中的 Grove 土壤湿度传感器](../../../../../translated_images/zh-CN/soil-moisture-sensor-in-soil.bfad91002bda5e96.webp)
## 编程土壤湿度传感器
现在可以为树莓派编程以使用连接的土壤湿度传感器。
### 任务 - 编程土壤湿度传感器
为设备编程。
1. 启动树莓派并等待其启动。
1. 启动 VS Code可以直接在树莓派上运行也可以通过 Remote SSH 扩展连接。
> ⚠️ 如果需要,可以参考[夜灯项目 - 第 1 课中设置和启动 VS Code 的说明](../../../1-getting-started/lessons/1-introduction-to-iot/pi.md)。
1. 在终端中,在 `pi` 用户的主目录下创建一个名为 `soil-moisture-sensor` 的新文件夹。在此文件夹中创建一个名为 `app.py` 的文件。
1. 在 VS Code 中打开此文件夹。
1. 在 `app.py` 文件中添加以下代码以导入一些所需的库:
```python
import time
from grove.adc import ADC
```
`import time` 语句导入了 `time` 模块,该模块将在稍后使用。
`from grove.adc import ADC` 语句从 Grove Python 库中导入了 `ADC`。该库包含与树莓派 Base Hat 上的模拟数字转换器交互并从模拟传感器读取电压的代码。
1. 在此代码下方添加以下代码以创建 `ADC` 类的实例:
```python
adc = ADC()
```
1. 添加一个无限循环,从 A0 引脚上的 ADC 读取数据,并将结果写入控制台。此循环可以在每次读取之间休眠 10 秒。
```python
while True:
soil_moisture = adc.read(0)
print("Soil moisture:", soil_moisture)
time.sleep(10)
```
1. 运行 Python 应用程序。您将在控制台中看到土壤湿度的测量值。向土壤中添加一些水,或者将传感器从土壤中移除,观察值的变化。
```output
pi@raspberrypi:~/soil-moisture-sensor $ python3 app.py
Soil moisture: 615
Soil moisture: 612
Soil moisture: 498
Soil moisture: 493
Soil moisture: 490
Soil Moisture: 388
```
在上面的示例输出中,您可以看到随着水的添加,电压下降。
> 💁 您可以在 [code/pi](../../../../../2-farm/lessons/2-detect-soil-moisture/code/pi) 文件夹中找到此代码。
😀 您的土壤湿度传感器程序运行成功!
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,112 @@
# 测量土壤湿度 - 虚拟物联网硬件
在本课的这一部分中,您将为虚拟物联网设备添加一个电容式土壤湿度传感器,并读取其数值。
## 虚拟硬件
虚拟物联网设备将使用模拟的 Grove 电容式土壤湿度传感器。这使得本实验与使用 Raspberry Pi 和物理 Grove 电容式土壤湿度传感器的操作保持一致。
在物理物联网设备中,土壤湿度传感器是一个电容式传感器,通过检测土壤的电容来测量土壤湿度。随着土壤湿度的变化,电容也会发生变化。当土壤湿度增加时,电压会降低。
这是一个模拟传感器,因此使用模拟的 10 位 ADC 来报告 1-1,023 范围内的数值。
### 将土壤湿度传感器添加到 CounterFit
要使用虚拟土壤湿度传感器,您需要将其添加到 CounterFit 应用中。
#### 任务 - 将土壤湿度传感器添加到 CounterFit
将土壤湿度传感器添加到 CounterFit 应用中。
1. 在您的电脑上创建一个名为 `soil-moisture-sensor` 的文件夹,并在其中创建一个名为 `app.py` 的单一文件,同时设置一个 Python 虚拟环境,并添加 CounterFit 的 pip 包。
> ⚠️ 如果需要,可以参考[第 1 课中关于创建和设置 CounterFit Python 项目的说明](../../../1-getting-started/lessons/1-introduction-to-iot/virtual-device.md)。
1. 确保 CounterFit 网页应用正在运行。
1. 创建一个土壤湿度传感器:
1. 在 *Sensors* 面板的 *Create sensor* 框中,点击 *Sensor type* 下拉框并选择 *Soil Moisture*
1. 将 *Units* 保持为 *NoUnits*
1. 确保 *Pin* 设置为 *0*
1. 点击 **Add** 按钮,在 Pin 0 上创建 *Soil Moisture* 传感器。
![土壤湿度传感器设置](../../../../../translated_images/zh-CN/counterfit-create-soil-moisture-sensor.35266135a5e0ae68b29a684d7db0d2933a8098b2307d197f7c71577b724603aa.png)
土壤湿度传感器将被创建并显示在传感器列表中。
![已创建的土壤湿度传感器](../../../../../translated_images/zh-CN/counterfit-soil-moisture-sensor.81742b2de0e9de60a3b3b9a2ff8ecc686d428eb6d71820f27a693be26e5aceee.png)
## 编写土壤湿度传感器应用程序
现在可以使用 CounterFit 传感器编写土壤湿度传感器应用程序。
### 任务 - 编写土壤湿度传感器应用程序
编写土壤湿度传感器应用程序。
1. 确保 `soil-moisture-sensor` 应用已在 VS Code 中打开。
1. 打开 `app.py` 文件。
1. 在 `app.py` 文件顶部添加以下代码,以连接应用到 CounterFit
```python
from counterfit_connection import CounterFitConnection
CounterFitConnection.init('127.0.0.1', 5000)
```
1. 在 `app.py` 文件中添加以下代码以导入一些所需的库:
```python
import time
from counterfit_shims_grove.adc import ADC
```
`import time` 语句导入了 `time` 模块,该模块将在后续任务中使用。
`from counterfit_shims_grove.adc import ADC` 语句导入了 `ADC` 类,用于与虚拟模拟到数字转换器交互,该转换器可以连接到 CounterFit 传感器。
1. 在此代码下方添加以下代码,以创建 `ADC` 类的实例:
```python
adc = ADC()
```
1. 添加一个无限循环,从 Pin 0 的 ADC 读取数据并将结果写入控制台。此循环可以在每次读取之间休眠 10 秒。
```python
while True:
soil_moisture = adc.read(0)
print("Soil moisture:", soil_moisture)
time.sleep(10)
```
1. 在 CounterFit 应用中更改土壤湿度传感器的值,该值将被应用程序读取。您可以通过以下两种方式进行更改:
* 在土壤湿度传感器的 *Value* 框中输入一个数字,然后点击 **Set** 按钮。您输入的数字将是传感器返回的值。
* 勾选 *Random* 复选框,并输入 *Min**Max* 值,然后点击 **Set** 按钮。每次传感器读取值时,它将读取一个介于 *Min**Max* 之间的随机数。
1. 运行 Python 应用程序。您将在控制台中看到土壤湿度测量值。更改 *Value**Random* 设置以查看值的变化。
```output
(.venv) ➜ soil-moisture-sensor $ python app.py
Soil moisture: 615
Soil moisture: 612
Soil moisture: 498
Soil moisture: 493
Soil moisture: 490
Soil Moisture: 388
```
> 💁 您可以在 [code/virtual-device](../../../../../2-farm/lessons/2-detect-soil-moisture/code/virtual-device) 文件夹中找到此代码。
😀 您的土壤湿度传感器程序运行成功!
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,106 @@
# 测量土壤湿度 - Wio Terminal
在本课中,您将为 Wio Terminal 添加一个电容式土壤湿度传感器,并读取其数值。
## 硬件
Wio Terminal 需要一个电容式土壤湿度传感器。
您将使用的传感器是 [电容式土壤湿度传感器](https://www.seeedstudio.com/Grove-Capacitive-Moisture-Sensor-Corrosion-Resistant.html),它通过检测土壤的电容来测量湿度。随着土壤湿度的变化,电容也会发生变化。当土壤湿度增加时,电压会降低。
这是一个模拟传感器,因此需要连接到 Wio Terminal 的模拟引脚,并使用板载 ADC 将其转换为 0-1023 的数值。
### 连接土壤湿度传感器
Grove 土壤湿度传感器可以连接到 Wio Terminal 的可配置模拟/数字端口。
#### 任务 - 连接土壤湿度传感器
连接土壤湿度传感器。
![一个 Grove 土壤湿度传感器](../../../../../translated_images/zh-CN/grove-capacitive-soil-moisture-sensor.e7f0776cce30e78be5cc5a07839385fd6718857f31b5bf5ad3d0c73c83b2f0ef.png)
1. 将 Grove 电缆的一端插入土壤湿度传感器上的插座。电缆只能以一种方向插入。
1. 在 Wio Terminal 未连接到电脑或其他电源的情况下,将 Grove 电缆的另一端连接到 Wio Terminal 屏幕右侧的 Grove 插座。这是距离电源按钮最远的插座。
![Grove 土壤湿度传感器连接到右侧插座](../../../../../translated_images/zh-CN/wio-soil-moisture-sensor.46919b61c3f6cb74.webp)
1. 将土壤湿度传感器插入土壤中。传感器上有一个“最高位置线”——一条白线横跨传感器。将传感器插入土壤,直到但不超过这条线。
![土壤中的 Grove 土壤湿度传感器](../../../../../translated_images/zh-CN/soil-moisture-sensor-in-soil.bfad91002bda5e96.webp)
1. 现在可以将 Wio Terminal 连接到您的电脑。
## 编程土壤湿度传感器
现在可以为 Wio Terminal 编程以使用连接的土壤湿度传感器。
### 任务 - 编程土壤湿度传感器
为设备编程。
1. 使用 PlatformIO 创建一个全新的 Wio Terminal 项目。将此项目命名为 `soil-moisture-sensor`。在 `setup` 函数中添加代码以配置串口。
> ⚠️ 如果需要,可以参考 [项目 1第 1 课中创建 PlatformIO 项目的说明](../../../1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md#create-a-platformio-project)。
1. 这个传感器没有专用库,但可以使用内置的 Arduino [`analogRead`](https://www.arduino.cc/reference/en/language/functions/analog-io/analogread/) 函数从模拟引脚读取数据。首先在 `setup` 函数中配置模拟引脚为输入,以便从中读取数值,添加以下代码:
```cpp
pinMode(A0, INPUT);
```
这将 `A0` 引脚(模拟/数字组合引脚)设置为输入引脚,可以从中读取电压。
1. 在 `loop` 函数中添加以下代码以从该引脚读取电压:
```cpp
int soil_moisture = analogRead(A0);
```
1. 在此代码下方添加以下代码,将数值打印到串口:
```cpp
Serial.print("Soil Moisture: ");
Serial.println(soil_moisture);
```
1. 最后在末尾添加一个 10 秒的延迟:
```cpp
delay(10000);
```
1. 构建并上传代码到 Wio Terminal。
> ⚠️ 如果需要,可以参考 [项目 1第 1 课中创建 PlatformIO 项目的说明](../../../1-getting-started/lessons/1-introduction-to-iot/wio-terminal.md#write-the-hello-world-app)。
1. 上传完成后,您可以使用串口监视器监测土壤湿度。向土壤中添加一些水,或者将传感器从土壤中移除,观察数值变化。
```output
> Executing task: platformio device monitor <
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at http://bit.ly/pio-monitor-filters
--- Miniterm on /dev/cu.usbmodem1201 9600,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
Soil Moisture: 526
Soil Moisture: 529
Soil Moisture: 521
Soil Moisture: 494
Soil Moisture: 454
Soil Moisture: 456
Soil Moisture: 395
Soil Moisture: 388
Soil Moisture: 394
Soil Moisture: 391
```
在上面的示例输出中,您可以看到随着水的添加,电压下降。
> 💁 您可以在 [code/wio-terminal](../../../../../2-farm/lessons/2-detect-soil-moisture/code/wio-terminal) 文件夹中找到此代码。
😀 您的土壤湿度传感器程序运行成功!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,303 @@
# 自动植物浇水
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-7.30b5f577d3cb8e031238751475cb519c7d6dbaea261b5df4643d086ffb2a03bb.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看大图。
本课程是 [Microsoft Reactor](https://developer.microsoft.com/reactor/?WT.mc_id=academic-17441-jabenn) 的 [IoT 初学者项目 2 - 数字农业系列](https://youtube.com/playlist?list=PLmsFUfdnGr3yCutmcVg6eAUEfsGiFXgcx) 的一部分。
[![物联网驱动的自动植物浇水](https://img.youtube.com/vi/g9FfZwv9R58/0.jpg)](https://youtu.be/g9FfZwv9R58)
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/13)
## 简介
在上一课中,你学习了如何监测土壤湿度。在本课中,你将学习如何构建一个响应土壤湿度的自动浇水系统的核心组件。你还将了解有关时间的知识——传感器可能需要一些时间来响应变化,而执行器可能需要时间来改变传感器测量的属性。
本课内容包括:
* [从低功率物联网设备控制高功率设备](../../../../../2-farm/lessons/3-automated-plant-watering)
* [控制继电器](../../../../../2-farm/lessons/3-automated-plant-watering)
* [通过 MQTT 控制你的植物](../../../../../2-farm/lessons/3-automated-plant-watering)
* [传感器和执行器的时间延迟](../../../../../2-farm/lessons/3-automated-plant-watering)
* [为植物控制服务器添加时间功能](../../../../../2-farm/lessons/3-automated-plant-watering)
## 从低功率物联网设备控制高功率设备
物联网设备使用低电压。这足以驱动传感器和低功率执行器(如 LED但不足以控制更大的硬件例如用于灌溉的水泵。即使是用于室内植物的小型水泵其电流需求也会超出物联网开发板的承受范围可能会烧毁电路板。
> 🎓 电流以安培 (A) 为单位测量,表示电路中流动的电量。电压提供推动力,而电流表示被推动的电量。你可以在 [维基百科的电流页面](https://wikipedia.org/wiki/Electric_current) 上了解更多。
解决方案是将水泵连接到外部电源,并使用执行器来打开水泵,就像你用手指打开灯一样。打开开关所需的能量很小(来自你身体的能量),而这会将灯连接到 110v/240v 的市电。
![一个灯开关打开灯的电源](../../../../../translated_images/zh-CN/light-switch.760317ad6ab8bd6d611da5352dfe9c73a94a0822ccec7df3c8bae35da18e1658.png)
> 🎓 [市电](https://wikipedia.org/wiki/Mains_electricity) 指的是通过国家基础设施输送到家庭和企业的电力。
✅ 物联网设备通常提供 3.3V 或 5V电流小于 1 安培 (1A)。相比之下,市电通常为 230V北美为 120V日本为 100V可以为需要 30A 电流的设备供电。
有许多执行器可以完成这一任务,包括可以附加到现有开关上的机械设备,模拟手指打开开关的动作。其中最常见的是继电器。
### 继电器
继电器是一种机电开关,它将电信号转换为机械运动,从而打开或关闭开关。继电器的核心是一个电磁铁。
> 🎓 [电磁铁](https://wikipedia.org/wiki/Electromagnet) 是通过电流流过线圈产生磁场的磁铁。当电流开启时,线圈被磁化;当电流关闭时,线圈失去磁性。
![当电磁铁通电时,产生磁场,打开输出电路的开关](../../../../../translated_images/zh-CN/relay-on.4db16a0fd6b66926.webp)
在继电器中,控制电路为电磁铁供电。当电磁铁通电时,它会拉动一个杠杆,移动开关,闭合一对触点,从而完成输出电路。
![当电磁铁断电时,不产生磁场,关闭输出电路的开关](../../../../../translated_images/zh-CN/relay-off.c34a178a2960fecd.webp)
当控制电路断电时,电磁铁关闭,释放杠杆并打开触点,从而关闭输出电路。继电器是一种数字执行器——高电平信号打开继电器,低电平信号关闭继电器。
输出电路可以用来为额外的硬件供电,例如灌溉系统。物联网设备可以打开继电器,完成为灌溉系统供电的输出电路,植物就会被浇水。然后物联网设备可以关闭继电器,切断灌溉系统的电源,停止供水。
![继电器打开,启动水泵向植物供水](../../../../../images/strawberry-pump.gif)
在上面的视频中,继电器被打开。继电器上的 LED 灯亮起,表示继电器已打开(某些继电器板上有 LED 指示灯显示继电器的状态),电源被送到水泵,启动水泵向植物供水。
> 💁 继电器还可以用于在两个输出电路之间切换,而不是简单地打开或关闭一个电路。当杠杆移动时,它会将开关从一个输出电路切换到另一个输出电路,通常共享一个公共电源连接或公共接地连接。
✅ 做一些研究:继电器有多种类型,差异包括控制电路在通电时是打开还是关闭继电器,或者是否有多个输出电路。了解这些不同类型的继电器。
当杠杆移动时,你通常可以听到它与电磁铁接触时发出的清脆的点击声。
> 💁 继电器可以被接线成在连接时实际上切断继电器的电源,从而关闭继电器,然后再次为继电器供电,如此循环。这会使继电器以极快的速度点击,发出嗡嗡声。这是一些早期电动门铃中蜂鸣器的工作原理。
### 继电器的功率
电磁铁所需的功率很小,可以通过物联网开发板的 3.3V 或 5V 输出控制。输出电路可以承载更多功率,具体取决于继电器,包括市电电压甚至更高的工业电压。这使得物联网开发板可以控制灌溉系统,从单个植物的小型水泵到整个商业农场的大型工业系统。
![一个带有标注的 Grove 继电器,显示控制电路、输出电路和继电器](../../../../../translated_images/zh-CN/grove-relay-labelled.293e068f5c3c2a199bd7892f2661fdc9e10c920b535cfed317fbd6d1d4ae1168.png)
上图显示了一个 Grove 继电器。控制电路连接到物联网设备,并使用 3.3V 或 5V 打开或关闭继电器。输出电路有两个端子,任意一个可以是电源或接地。输出电路可以处理高达 250V、10A 的电流,足以驱动一系列市电设备。你还可以找到能够处理更高功率的继电器。
![通过继电器连接的水泵](../../../../../translated_images/zh-CN/pump-wired-to-relay.66c5cfc0d8918990.webp)
在上图中,电源通过继电器供给水泵。一根红线将 USB 电源的 +5V 端子连接到继电器输出电路的一个端子,另一根红线将输出电路的另一个端子连接到水泵。一根黑线将水泵连接到 USB 电源的接地端。当继电器打开时,它完成电路,将 5V 送到水泵,启动水泵。
## 控制继电器
你可以通过物联网开发板控制继电器。
### 任务 - 控制继电器
按照相关指南,通过你的物联网设备控制继电器:
* [Arduino - Wio Terminal](wio-terminal-relay.md)
* [单板计算机 - Raspberry Pi](pi-relay.md)
* [单板计算机 - 虚拟设备](virtual-device-relay.md)
## 通过 MQTT 控制你的植物
目前,你的继电器是由物联网设备直接根据单个土壤湿度读数控制的。在商业灌溉系统中,控制逻辑通常是集中化的,这样可以使用多个传感器的数据来决定是否浇水,并且可以在一个地方更改所有配置。为了模拟这一点,你可以通过 MQTT 控制继电器。
### 任务 - 通过 MQTT 控制继电器
1. 在你的 `soil-moisture-sensor` 项目中添加相关的 MQTT 库/pip 包和代码以连接到 MQTT。将客户端 ID 命名为 `soilmoisturesensor_client`,并在前面加上你的 ID。
> ⚠️ 如果需要,可以参考 [项目 1第 4 课中连接到 MQTT 的说明](../../../1-getting-started/lessons/4-connect-internet/README.md#connect-your-iot-device-to-mqtt)。
1. 添加相关设备代码以发送带有土壤湿度设置的遥测数据。对于遥测消息,将属性命名为 `soil_moisture`
> ⚠️ 如果需要,可以参考 [项目 1第 4 课中发送遥测数据到 MQTT 的说明](../../../1-getting-started/lessons/4-connect-internet/README.md#send-telemetry-from-your-iot-device)。
1. 在名为 `soil-moisture-sensor-server` 的文件夹中创建一些本地服务器代码,用于订阅遥测数据并发送命令以控制继电器。将命令消息中的属性命名为 `relay_on`,并将客户端 ID 命名为 `soilmoisturesensor_server`,在前面加上你的 ID。保持与项目 1第 4 课中编写的服务器代码相同的结构,因为你将在本课后续部分中添加到此代码。
> ⚠️ 如果需要,可以参考 [发送遥测数据到 MQTT 的说明](../../../1-getting-started/lessons/4-connect-internet/README.md#write-the-server-code) 和 [通过 MQTT 发送命令](../../../1-getting-started/lessons/4-connect-internet/README.md#send-commands-to-the-mqtt-broker) 的说明。
1. 添加相关设备代码以根据接收到的命令控制继电器,使用消息中的 `relay_on` 属性。如果 `soil_moisture` 大于 450则发送 true 给 `relay_on`,否则发送 false与之前为物联网设备添加的逻辑相同。
> ⚠️ 如果需要,可以参考 [项目 1第 4 课中响应 MQTT 命令的说明](../../../1-getting-started/lessons/4-connect-internet/README.md#handle-commands-on-the-iot-device)。
> 💁 你可以在 [code-mqtt](../../../../../2-farm/lessons/3-automated-plant-watering/code-mqtt) 文件夹中找到此代码。
确保代码在你的设备和本地服务器上运行,并通过更改土壤湿度水平进行测试,无论是通过更改虚拟传感器发送的值,还是通过向土壤中添加水或将传感器从土壤中移除来改变湿度。
## 传感器和执行器的时间延迟
在第 3 课中你构建了一个夜灯——当光传感器检测到低光水平时LED 会立即点亮。光传感器能够即时检测光线变化,设备也能快速响应,仅受 `loop` 函数或 `while True:` 循环中延迟的限制。作为物联网开发者,你不能总是依赖如此快速的反馈循环。
### 土壤湿度的时间延迟
如果你在上一课中使用了物理传感器测量土壤湿度,你可能注意到在给植物浇水后,土壤湿度读数需要几秒钟才会下降。这并不是因为传感器反应慢,而是因为水需要时间渗透到土壤中。
💁 如果你在传感器附近浇水过多,可能会看到读数迅速下降,然后又回升——这是因为传感器附近的水分扩散到土壤的其他部分,导致传感器附近的土壤湿度降低。
![土壤湿度测量值为658在浇水时没有变化只有当水渗透到土壤后才会降到320](../../../../../translated_images/zh-CN/soil-moisture-travel.a0e31af222cf1438.webp)
在上图中土壤湿度的读数为658。植物被浇水但这个读数不会立即变化因为水还没有到达传感器。甚至在水到达传感器之前浇水可能已经结束只有当水渗透到土壤后读数才会下降以反映新的湿度水平。
如果你正在编写代码,通过继电器根据土壤湿度水平控制灌溉系统,你需要考虑这种延迟,并在你的物联网设备中构建更智能的时间控制。
✅ 花点时间思考一下你会如何处理这种情况。
### 控制传感器和执行器的时间
假设你被要求为一个农场构建一个灌溉系统。根据土壤类型发现植物的理想土壤湿度水平对应于模拟电压读数为400-450。
你可以像夜灯一样编程——只要传感器读数高于450就打开继电器启动水泵。问题在于从水泵到土壤再到传感器水需要一段时间。传感器在检测到450的水平时会停止供水但由于泵送的水继续渗透到土壤中湿度水平会继续下降。最终结果是浪费水并可能损害植物根部。
✅ 记住——过多的水和过少的水对植物来说都可能是有害的,同时也浪费了宝贵的资源。
更好的解决方案是理解从执行器启动到传感器读数变化之间存在延迟。这意味着传感器不仅需要等待一段时间再测量值,执行器也需要在下一次传感器测量之前关闭一段时间。
每次继电器应该开启多长时间?最好谨慎行事,只让继电器开启很短的时间,然后等待水渗透,再重新检查湿度水平。毕竟,你可以随时再次打开继电器添加更多的水,但你无法从土壤中移除多余的水。
> 💁 这种时间控制非常具体,取决于你正在构建的物联网设备、测量的属性以及使用的传感器和执行器。
![一个草莓植物通过水泵连接到水源,水泵通过继电器控制。继电器和土壤湿度传感器都连接到树莓派](../../../../../translated_images/zh-CN/strawberry-with-pump.b410fc72ac6aabad.webp)
例如我有一株草莓植物配备了一个土壤湿度传感器和一个通过继电器控制的水泵。我观察到当我加水时土壤湿度读数需要大约20秒才能稳定。这意味着我需要关闭继电器并等待20秒然后再检查湿度水平。我宁愿水少一点也不愿多——我可以随时再次打开水泵但我无法从植物中移除多余的水。
![步骤1测量湿度。步骤2加水。步骤3等待水渗透到土壤中。步骤4重新测量湿度](../../../../../translated_images/zh-CN/soil-moisture-delay.865f3fae206db01d.webp)
这意味着最佳的浇水流程可能是这样的:
* 打开水泵5秒
* 等待20秒
* 检查土壤湿度
* 如果湿度水平仍然高于所需值,重复上述步骤
5秒可能对水泵来说太长尤其是当湿度水平仅略高于所需值时。确定使用什么时间的最佳方法是尝试然后根据传感器数据进行调整形成一个持续的反馈循环。这甚至可以导致更精细的时间控制例如每高于所需湿度100就打开水泵1秒而不是固定的5秒。
✅ 做些研究:是否还有其他时间因素需要考虑?是否可以在任何时候浇水,只要土壤湿度过低,还是有特定的时间适合或不适合给植物浇水?
> 💁 在控制室外种植的自动灌溉系统时,还可以考虑天气预报。如果预计会下雨,那么可以暂停浇水,等到雨停后再决定是否需要浇水。这样可以更高效地利用水资源,而不是在下雨前浪费水。
## 为植物控制服务器添加时间控制
可以修改服务器代码,为浇水周期的时间控制和等待土壤湿度水平变化添加逻辑。控制继电器时间的服务器逻辑如下:
1. 接收到遥测消息
1. 检查土壤湿度水平
1. 如果湿度正常,则不做任何操作。如果读数过高(表示土壤湿度过低),则:
1. 发送命令打开继电器
1. 等待5秒
1. 发送命令关闭继电器
1. 等待20秒让土壤湿度水平稳定
浇水周期从接收到遥测消息到准备再次处理土壤湿度水平大约需要25秒。我们每10秒发送一次土壤湿度水平因此在服务器等待土壤湿度水平稳定时会有消息重叠这可能会启动另一个浇水周期。
有两种方法可以解决这个问题:
* 更改物联网设备代码,使其每分钟只发送一次遥测数据,这样浇水周期会在下一条消息发送之前完成
* 在浇水周期期间取消订阅遥测数据
第一种方法并不总是适合大型农场。农民可能希望在浇水时捕获土壤湿度水平,以便稍后分析,例如了解农场不同区域的水流情况,从而指导更有针对性的浇水。第二种方法更好——代码只是忽略无法使用的遥测数据,但遥测数据仍然可以供其他订阅的服务使用。
> 💁 物联网数据并不是从一个设备发送到一个服务,而是许多设备可以将数据发送到一个代理,许多服务可以从代理中监听数据。例如,一个服务可以监听土壤湿度数据并将其存储到数据库中以供稍后分析。另一个服务也可以监听相同的遥测数据来控制灌溉系统。
### 任务 - 为植物控制服务器添加时间控制
更新你的服务器代码使继电器运行5秒然后等待20秒。
1. 如果尚未打开,请在 VS Code 中打开 `soil-moisture-sensor-server` 文件夹。确保虚拟环境已激活。
1. 打开 `app.py` 文件
1. 在现有导入语句下方,添加以下代码到 `app.py` 文件中:
```python
import threading
```
这条语句从 Python 库中导入 `threading`,它允许 Python 在等待时执行其他代码。
1. 在处理服务器代码接收的遥测消息的 `handle_telemetry` 函数之前,添加以下代码:
```python
water_time = 5
wait_time = 20
```
这定义了继电器运行的时间(`water_time`)以及之后检查土壤湿度的等待时间(`wait_time`)。
1. 在这段代码下方,添加以下内容:
```python
def send_relay_command(client, state):
command = { 'relay_on' : state }
print("Sending message:", command)
client.publish(server_command_topic, json.dumps(command))
```
这段代码定义了一个名为 `send_relay_command` 的函数,用于通过 MQTT 发送命令来控制继电器。遥测数据被创建为一个字典,然后转换为 JSON 字符串。传递给 `state` 的值决定继电器是打开还是关闭。
1. 在 `send_relay_code` 函数之后,添加以下代码:
```python
def control_relay(client):
print("Unsubscribing from telemetry")
mqtt_client.unsubscribe(client_telemetry_topic)
send_relay_command(client, True)
time.sleep(water_time)
send_relay_command(client, False)
time.sleep(wait_time)
print("Subscribing to telemetry")
mqtt_client.subscribe(client_telemetry_topic)
```
这定义了一个基于所需时间控制继电器的函数。它首先取消订阅遥测数据,以便在浇水期间不处理土壤湿度消息。接着发送命令打开继电器,然后等待 `water_time`,再发送命令关闭继电器。最后,它等待 `wait_time` 秒让土壤湿度水平稳定,然后重新订阅遥测数据。
1. 将 `handle_telemetry` 函数更改为以下内容:
```python
def handle_telemetry(client, userdata, message):
payload = json.loads(message.payload.decode())
print("Message received:", payload)
if payload['soil_moisture'] > 450:
threading.Thread(target=control_relay, args=(client,)).start()
```
这段代码检查土壤湿度水平。如果大于450说明土壤需要浇水于是调用 `control_relay` 函数。这个函数在一个单独的线程中运行,在后台执行。
1. 确保你的物联网设备正在运行然后运行这段代码。更改土壤湿度水平观察继电器的行为——它应该打开5秒然后至少保持关闭20秒只有在土壤湿度水平不足时才会再次打开。
```output
(.venv) ➜ soil-moisture-sensor-server ✗ python app.py
Message received: {'soil_moisture': 457}
Unsubscribing from telemetry
Sending message: {'relay_on': True}
Sending message: {'relay_on': False}
Subscribing to telemetry
Message received: {'soil_moisture': 302}
```
在模拟灌溉系统中测试这一点的一个好方法是使用干燥的土壤,然后在继电器打开时手动倒水,在继电器关闭时停止倒水。
> 💁 你可以在 [code-timing](../../../../../2-farm/lessons/3-automated-plant-watering/code-timing) 文件夹中找到这段代码。
> 💁 如果你想使用水泵构建一个真实的灌溉系统,可以使用 [6V 水泵](https://www.seeedstudio.com/6V-Mini-Water-Pump-p-1945.html) 和 [USB 端子电源](https://www.adafruit.com/product/3628)。确保泵的电源或输出通过继电器连接。
---
## 🚀 挑战
你能想到其他类似的问题吗?例如其他物联网设备或电气设备,它们的执行器作用需要一段时间才能影响传感器的读数。你家里或学校可能就有几个这样的例子。
* 它们测量什么属性?
* 执行器作用后,属性需要多长时间才能改变?
* 属性超过所需值是否可以接受?
* 如果需要,如何将属性恢复到所需值?
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/14)
## 复习与自学
* 阅读更多关于继电器的信息,包括它们在电话交换机中的历史用途:[继电器 - 维基百科](https://wikipedia.org/wiki/Relay)。
## 作业
[构建一个更高效的浇水周期](assignment.md)
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,43 @@
# 构建更高效的灌溉周期
## 指导说明
本课程讲解了如何通过传感器数据控制继电器,而继电器可以进一步控制灌溉系统的水泵。对于一块特定的土壤,运行水泵固定的时间长度对土壤湿度的影响应该是恒定的。这意味着你可以了解灌溉几秒钟会导致土壤湿度读数下降多少。利用这些数据,你可以构建一个更可控的灌溉系统。
在本次作业中,你需要计算水泵运行多长时间才能使土壤湿度达到特定的上升值。
> ⚠️ 如果你使用的是虚拟物联网硬件,你可以完成这个过程,但需要通过在继电器开启时每秒手动增加固定的土壤湿度读数来模拟结果。
1. 从干燥的土壤开始。测量土壤湿度。
1. 添加固定量的水可以通过运行水泵1秒钟或倒入固定量的水来实现。
> 水泵应始终以恒定的速率运行,因此每秒运行时应提供相同量的水。
1. 等待土壤湿度水平稳定后进行读数。
1. 重复多次并创建一个结果表格。以下是该表格的示例:
| 水泵总运行时间 | 土壤湿度 | 减少值 |
| --- | --: | -: |
| 干燥 | 643 | 0 |
| 1秒 | 621 | 22 |
| 2秒 | 601 | 20 |
| 3秒 | 579 | 22 |
| 4秒 | 560 | 19 |
| 5秒 | 539 | 21 |
| 6秒 | 521 | 18 |
1. 计算每秒供水导致的土壤湿度平均增加值。在上述示例中每秒供水使读数平均减少了20.3。
1. 使用这些数据改进服务器代码的效率,使水泵运行所需的时间正好达到目标土壤湿度水平。
## 评分标准
| 标准 | 优秀 | 合格 | 需要改进 |
| ---- | ---- | ---- | -------- |
| 捕获土壤湿度数据 | 能够在添加固定量的水后捕获多次读数 | 能够在添加固定量的水后捕获部分读数 | 只能捕获一两次读数,或无法使用固定量的水 |
| 校准服务器代码 | 能够计算土壤湿度读数的平均减少值,并更新服务器代码以使用该值 | 能够计算平均减少值,但无法更新服务器代码,或无法正确计算平均值,但使用该值正确更新了服务器代码 | 无法计算平均值,或无法更新服务器代码 |
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。虽然我们尽力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们不对因使用此翻译而产生的任何误解或误读承担责任。

@ -0,0 +1,112 @@
# 控制继电器 - 树莓派
在本节课程中,您将为树莓派添加一个继电器,除了土壤湿度传感器外,还可以根据土壤湿度水平控制继电器。
## 硬件
树莓派需要一个继电器。
您将使用的继电器是 [Grove 继电器](https://www.seeedstudio.com/Grove-Relay.html),这是一个常开继电器(意味着当没有信号发送到继电器时,输出电路是断开的)。它可以处理高达 250V 和 10A 的输出电路。
这是一个数字执行器,因此需要连接到 Grove Base Hat 上的数字引脚。
### 连接继电器
Grove 继电器可以连接到树莓派。
#### 任务
连接继电器。
![一个 Grove 继电器](../../../../../translated_images/zh-CN/grove-relay.d426958ca210fbd0fb7983d7edc069d46c73a8b0a099d94797bd756f7b6bb6be.png)
1. 将 Grove 电缆的一端插入继电器上的插座。它只能以一种方式插入。
1. 在树莓派断电的情况下,将 Grove 电缆的另一端连接到 Grove Base Hat 上标记为 **D5** 的数字插座。这个插座位于 GPIO 引脚旁边的一排插座中,从左数第二个。保持土壤湿度传感器连接到 **A0** 插座。
![Grove 继电器连接到 D5 插座,土壤湿度传感器连接到 A0 插座](../../../../../translated_images/zh-CN/pi-relay-and-soil-moisture-sensor.02f3198975b8c53e69ec716cd2719ce117700bd1fc933eaf93476c103c57939b.png)
1. 如果土壤湿度传感器还没有插入土壤,请将其插入土壤中(如果您在上一节课程中已经插入,则无需重复操作)。
## 编程继电器
现在可以为树莓派编程以使用连接的继电器。
### 任务
编程设备。
1. 启动树莓派并等待其启动完成。
1. 在 VS Code 中打开上一节课程中的 `soil-moisture-sensor` 项目(如果尚未打开)。您将在此项目中进行添加。
1. 在现有的导入语句下,向 `app.py` 文件添加以下代码:
```python
from grove.grove_relay import GroveRelay
```
这条语句从 Grove Python 库中导入 `GroveRelay`,以便与 Grove 继电器交互。
1. 在 `ADC` 类的声明下方添加以下代码,以创建一个 `GroveRelay` 实例:
```python
relay = GroveRelay(5)
```
这将在您连接继电器的数字引脚 **D5** 上创建一个继电器。
1. 为了测试继电器是否正常工作,在 `while True:` 循环中添加以下代码:
```python
relay.on()
time.sleep(.5)
relay.off()
```
这段代码会打开继电器,等待 0.5 秒,然后关闭继电器。
1. 运行 Python 应用程序。继电器将每 10 秒打开和关闭一次,每次打开和关闭之间有 0.5 秒的延迟。您会听到继电器点击打开然后点击关闭。当继电器打开时Grove 板上的 LED 会亮起当继电器关闭时LED 会熄灭。
![继电器打开和关闭](../../../../../images/relay-turn-on-off.gif)
## 根据土壤湿度控制继电器
现在继电器可以正常工作了,可以根据土壤湿度传感器的读数来控制它。
### 任务
控制继电器。
1. 删除您添加的用于测试继电器的 3 行代码。用以下代码替换它们:
```python
if soil_moisture > 450:
print("Soil Moisture is too low, turning relay on.")
relay.on()
else:
print("Soil Moisture is ok, turning relay off.")
relay.off()
```
这段代码会从土壤湿度传感器读取土壤湿度水平。如果湿度值高于 450则打开继电器如果低于 450则关闭继电器。
> 💁 请记住,电容式土壤湿度传感器的读数越低,土壤中的湿度越高,反之亦然。
1. 运行 Python 应用程序。您会看到继电器根据土壤湿度水平打开或关闭。尝试在干燥的土壤中测试,然后加水。
```output
Soil Moisture: 638
Soil Moisture is too low, turning relay on.
Soil Moisture: 452
Soil Moisture is too low, turning relay on.
Soil Moisture: 347
Soil Moisture is ok, turning relay off.
```
> 💁 您可以在 [code-relay/pi](../../../../../2-farm/lessons/3-automated-plant-watering/code-relay/pi) 文件夹中找到这段代码。
😀 恭喜!您的土壤湿度传感器成功控制了继电器!
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而产生的任何误解或误读不承担责任。

@ -0,0 +1,116 @@
# 控制继电器 - 虚拟物联网硬件
在本节课程中,您将为虚拟物联网设备添加一个继电器,除了土壤湿度传感器外,还可以根据土壤湿度水平来控制继电器。
## 虚拟硬件
虚拟物联网设备将使用模拟的 Grove 继电器。这使得本实验与使用带有物理 Grove 继电器的 Raspberry Pi 的操作保持一致。
在物理物联网设备中,继电器通常是常开型继电器(即当没有信号发送到继电器时,输出电路是断开的或未连接的)。这种继电器可以处理高达 250V 和 10A 的输出电路。
### 将继电器添加到 CounterFit
要使用虚拟继电器,您需要将其添加到 CounterFit 应用中。
#### 任务
将继电器添加到 CounterFit 应用。
1. 如果尚未打开,请在 VS Code 中打开上一节课中的 `soil-moisture-sensor` 项目。您将基于此项目进行添加。
1. 确保 CounterFit 网页应用正在运行。
1. 创建一个继电器:
1. 在 *Actuators* 面板的 *Create actuator* 框中,点击 *Actuator type* 下拉框并选择 *Relay*
1. 将 *Pin* 设置为 *5*
1. 点击 **Add** 按钮,在 Pin 5 上创建继电器。
![继电器设置](../../../../../translated_images/zh-CN/counterfit-create-relay.fa7c40fd0f2f6afc33b35ea94fcb235085be4861e14e3fe6b9b7bcfc82d1c888.png)
继电器将被创建并显示在执行器列表中。
![创建的继电器](../../../../../translated_images/zh-CN/counterfit-relay.bbf74c1dbdc8b9acd983367fcbd06703a402aefef6af54ddb28e11307ba8a12c.png)
## 编程继电器
现在可以为土壤湿度传感器应用编程以使用虚拟继电器。
### 任务
编程虚拟设备。
1. 如果尚未打开,请在 VS Code 中打开上一节课中的 `soil-moisture-sensor` 项目。您将基于此项目进行添加。
1. 在现有的导入代码下方,将以下代码添加到 `app.py` 文件中:
```python
from counterfit_shims_grove.grove_relay import GroveRelay
```
此语句从 Grove Python shim 库中导入 `GroveRelay`,以与虚拟 Grove 继电器交互。
1. 在 `ADC` 类的声明下方添加以下代码,以创建一个 `GroveRelay` 实例:
```python
relay = GroveRelay(5)
```
这将在 Pin **5** 上创建一个继电器,这是您连接继电器的引脚。
1. 为测试继电器是否正常工作,在 `while True:` 循环中添加以下代码:
```python
relay.on()
time.sleep(.5)
relay.off()
```
代码将继电器打开,等待 0.5 秒,然后关闭继电器。
1. 运行 Python 应用。继电器将每 10 秒打开和关闭一次,打开和关闭之间有半秒的延迟。您将在 CounterFit 应用中看到虚拟继电器随着继电器的打开和关闭而闭合和断开。
![虚拟继电器打开和关闭](../../../../../images/virtual-relay-turn-on-off.gif)
## 根据土壤湿度控制继电器
现在继电器可以正常工作了,可以根据土壤湿度读数来控制它。
### 任务
控制继电器。
1. 删除您添加的用于测试继电器的 3 行代码。用以下代码替换它们:
```python
if soil_moisture > 450:
print("Soil Moisture is too low, turning relay on.")
relay.on()
else:
print("Soil Moisture is ok, turning relay off.")
relay.off()
```
此代码从土壤湿度传感器检查土壤湿度水平。如果湿度值高于 450则打开继电器如果低于 450则关闭继电器。
> 💁 请记住,电容式土壤湿度传感器的读数越低,土壤中的湿度越高,反之亦然。
1. 运行 Python 应用。您将看到继电器根据土壤湿度水平打开或关闭。更改土壤湿度传感器的 *Value**Random* 设置以查看值变化。
```output
Soil Moisture: 638
Soil Moisture is too low, turning relay on.
Soil Moisture: 452
Soil Moisture is too low, turning relay on.
Soil Moisture: 347
Soil Moisture is ok, turning relay off.
```
> 💁 您可以在 [code-relay/virtual-device](../../../../../2-farm/lessons/3-automated-plant-watering/code-relay/virtual-device) 文件夹中找到此代码。
😀 恭喜!您的虚拟土壤湿度传感器成功控制了继电器程序!
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于重要信息,建议使用专业人工翻译。我们对因使用此翻译而产生的任何误解或误读不承担责任。

@ -0,0 +1,14 @@
# 控制继电器 - Wio Terminal
在本节课程中,您将为 Wio Terminal 添加一个继电器,除了土壤湿度传感器外,还可以根据土壤湿度水平控制继电器。
## 硬件
Wio Terminal 需要一个继电器。
您将使用的是 [Grove 继电器](https://www.seeedstudio.com/Grove-Relay.html),这是一个常开继电器(意味着当没有信号发送到继电器时,输出电路是断开的)。它可以处理高达 250V 和 10A 的输出电路。
这是一个数字执行器,因此需要连接到 Wio Terminal 的数字引脚。模拟/数字组合端口已经被土壤湿度传感器占用,因此继电器需要插入另一个端口,这是一个组合 I
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,440 @@
# 将植物迁移到云端
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-8.3f21f3c11159e6a0a376351973ea5724d5de68fa23b4288853a174bed9ac48c3.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看大图。
本课程是 [Microsoft Reactor](https://developer.microsoft.com/reactor/?WT.mc_id=academic-17441-jabenn) 的 [IoT 初学者项目 2 - 数字农业系列](https://youtube.com/playlist?list=PLmsFUfdnGr3yCutmcVg6eAUEfsGiFXgcx) 的一部分。
[![使用 Azure IoT Hub 将设备连接到云端](https://img.youtube.com/vi/bNxjopXkhvk/0.jpg)](https://youtu.be/bNxjopXkhvk)
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/15)
## 简介
在上一课中,你学习了如何将植物连接到 MQTT broker并通过本地运行的服务器代码控制继电器。这构成了从家中单个植物到商业农场使用的联网自动灌溉系统的核心。
IoT 设备通过公共 MQTT broker 进行通信,以演示原理,但这种方式并不是最可靠或安全的。在本课中,你将学习云端以及公共云服务提供的 IoT 功能。你还将学习如何将植物从公共 MQTT broker 迁移到这些云服务之一。
本课内容包括:
* [什么是云?](../../../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud)
* [创建云订阅](../../../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud)
* [云 IoT 服务](../../../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud)
* [在云端创建 IoT 服务](../../../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud)
* [与 IoT Hub 通信](../../../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud)
* [将设备连接到 IoT 服务](../../../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud)
## 什么是云?
在云出现之前,当公司想为员工(如数据库或文件存储)或公众(如网站)提供服务时,他们需要建立并运行数据中心。这可能是一个有少量计算机的房间,也可能是一个有许多计算机的建筑。公司需要管理所有事情,包括:
* 购买计算机
* 硬件维护
* 电力和冷却
* 网络连接
* 安全性,包括建筑安全和计算机软件安全
* 软件安装和更新
这可能非常昂贵,需要广泛的专业员工,并且在需要时改变速度非常慢。例如,如果一家在线商店需要为繁忙的假期季节做计划,他们需要提前几个月购买更多硬件、配置、安装并安装运行销售流程的软件。假期季节结束后,销售量下降,他们会留下闲置的计算机,直到下一个繁忙季节。
✅ 你认为这种方式能让公司快速行动吗?如果一家在线服装零售商因为某位名人穿着他们的服装而突然流行起来,他们能否快速增加计算能力以支持突然涌入的订单?
### 别人的计算机
云通常被戏称为“别人的计算机”。最初的想法很简单——与其购买计算机,不如租用别人的计算机。云计算提供商会管理巨大的数据中心。他们负责购买和安装硬件、管理电力和冷却、网络连接、建筑安全、硬件和软件更新等所有事情。作为客户,你可以根据需求租用计算机,需求增加时租用更多,需求减少时减少租用。这些云数据中心分布在全球各地。
![一个微软云数据中心](../../../../../translated_images/zh-CN/azure-region-existing.73f704604f2aa6cb9b5a49ed40e93d4fd81ae3f4e6af4a8ca504023902832f56.png)
![一个微软云数据中心的计划扩展](../../../../../translated_images/zh-CN/azure-region-planned-expansion.a5074a1e8af74f156a73552d502429e5b126ea5019274d767ecb4b9afdad442b.png)
这些数据中心的面积可以达到数平方公里。上图是几年前拍摄的微软云数据中心,展示了初始规模以及计划扩展。扩展区域的面积超过 5 平方公里。
> 💁 这些数据中心需要大量电力,有些甚至拥有自己的发电站。由于规模和云提供商的投资,它们通常非常环保。它们比大量小型数据中心更高效,主要使用可再生能源,云提供商努力减少浪费、降低水资源使用,并重新种植森林以弥补因建设数据中心而砍伐的树木。你可以在 [Azure 可持续发展网站](https://azure.microsoft.com/global-infrastructure/sustainability/?WT.mc_id=academic-17441-jabenn) 上了解更多关于云提供商如何致力于可持续发展的信息。
✅ 做一些研究:了解主要的云服务,例如 [微软的 Azure](https://azure.microsoft.com/?WT.mc_id=academic-17441-jabenn) 或 [谷歌的 GCP](https://cloud.google.com)。它们有多少个数据中心,这些数据中心分布在哪里?
使用云可以降低公司的成本,让公司专注于自己的核心业务,将云计算的专业知识交给提供商。公司不再需要租用或购买数据中心空间、支付不同的供应商以获得连接和电力,也不需要雇佣专家。相反,他们可以向云提供商支付一张月度账单,所有事情都由云提供商处理。
云提供商可以利用规模经济降低成本,例如批量购买计算机以获得更低价格、投资工具以减少维护工作量,甚至设计和制造自己的硬件以改进云服务。
### 微软 Azure
Azure 是微软的开发者云,也是你将在这些课程中使用的云服务。以下视频简要介绍了 Azure
[![Azure 概览视频](../../../../../translated_images/zh-CN/what-is-azure-video-thumbnail.20174db09e03bbb8.webp)](https://www.microsoft.com/videoplayer/embed/RE4Ibng?WT.mc_id=academic-17441-jabenn)
## 创建云订阅
要使用云服务,你需要向云提供商注册订阅。在本课中,你将注册一个微软 Azure 订阅。如果你已经有 Azure 订阅,可以跳过此任务。以下描述的订阅详情在撰写时是正确的,但可能会有所变化。
> 💁 如果你通过学校访问这些课程,你可能已经有一个可用的 Azure 订阅。请向你的老师确认。
你可以注册两种不同类型的免费 Azure 订阅:
* **Azure for Students** - 这是为 18 岁以上学生设计的订阅。注册时不需要信用卡,你可以使用学校邮箱地址验证自己是学生。注册后,你将获得 100 美元的云资源使用额度,以及包括免费版 IoT 服务在内的免费服务。订阅有效期为 12 个月,每年可以续订,只要你仍是学生。
* **Azure 免费订阅** - 这是为非学生设计的订阅。注册时需要信用卡,但不会扣费,仅用于验证你是真人而不是机器人。你可以在前 30 天内获得 200 美元的信用额度,用于任何服务,同时享受 Azure 服务的免费层。一旦信用额度用完,除非你转换为按需付费订阅,否则不会扣费。
> 💁 微软确实为 18 岁以下学生提供了 Azure for Students Starter 订阅,但在撰写时它不支持任何 IoT 服务。
### 任务 - 注册免费云订阅
如果你是 18 岁以上的学生,可以注册 Azure for Students 订阅。你需要使用学校邮箱地址验证身份。你可以通过以下两种方式完成:
* 在 [education.github.com/pack](https://education.github.com/pack) 注册 GitHub 学生开发者包。这将为你提供一系列工具和优惠,包括 GitHub 和微软 Azure。注册开发者包后你可以激活 Azure for Students 优惠。
* 直接在 [azure.microsoft.com/free/students](https://azure.microsoft.com/free/students/?WT.mc_id=academic-17441-jabenn) 注册 Azure for Students 账户。
> ⚠️ 如果你的学校邮箱地址未被识别,请在 [此仓库中提交问题](https://github.com/Microsoft/IoT-For-Beginners/issues),我们将尝试将其添加到 Azure for Students 允许列表中。
如果你不是学生,或者没有有效的学校邮箱地址,可以注册 Azure 免费订阅。
* 在 [azure.microsoft.com/free](https://azure.microsoft.com/free/?WT.mc_id=academic-17441-jabenn) 注册 Azure 免费订阅。
## 云 IoT 服务
你使用的公共测试 MQTT broker 是一个很好的学习工具,但作为商业工具有以下缺点:
* 可靠性 - 它是一个免费服务,没有任何保证,随时可能关闭
* 安全性 - 它是公共的,任何人都可以监听你的遥测数据或发送命令控制你的硬件
* 性能 - 它设计用于少量测试消息,无法处理大量消息
* 发现 - 无法知道哪些设备已连接
云中的 IoT 服务解决了这些问题。它们由大型云提供商维护,这些提供商在可靠性方面投入巨大,并随时解决可能出现的问题。它们内置了安全性,防止黑客读取你的数据或发送恶意命令。它们还具有高性能,能够每天处理数百万条消息,并利用云根据需求进行扩展。
> 💁 虽然这些服务需要支付月费,但大多数云提供商提供免费版 IoT 服务,限制每天的消息数量或可连接的设备数量。对于开发者学习服务来说,这个免费版通常已经足够。在本课中,你将使用免费版。
IoT 设备通过设备 SDK一个提供服务功能代码的库或直接通过通信协议如 MQTT 或 HTTP连接到云服务。设备 SDK 通常是最简单的选择,因为它会处理所有事情,例如知道要发布或订阅哪些主题,以及如何处理安全性。
![设备通过设备 SDK 连接到服务。服务器代码也通过 SDK 连接到服务](../../../../../translated_images/zh-CN/iot-service-connectivity.7e873847921a5d6fd60d0ba3a943210194518cee0d4e362476624316443275c3.png)
你的设备随后通过该服务与应用程序的其他部分通信——类似于你通过 MQTT 发送遥测数据和接收命令。通常使用服务 SDK 或类似库。消息从设备发送到服务,应用程序的其他组件可以读取这些消息,然后将消息发送回设备。
![没有有效密钥的设备无法连接到 IoT 服务](../../../../../translated_images/zh-CN/iot-service-allowed-denied-connection.818b0063ac213fb84204a7229303764d9b467ca430fb822b4ac2fca267d56726.png)
这些服务通过了解所有可以连接并发送数据的设备来实现安全性,方法是预先注册设备,或者为设备提供密钥或证书,使它们能够在首次连接时自行注册到服务。未知设备无法连接,如果尝试连接,服务会拒绝连接并忽略它们发送的消息。
✅ 做一些研究:开放的 IoT 服务允许任何设备或代码连接会有什么缺点?你能找到黑客利用这种服务的具体例子吗?
应用程序的其他组件可以连接到 IoT 服务,了解所有已连接或注册的设备,并直接与它们单独或批量通信。
💁 物联网服务还实现了额外的功能,云服务提供商也提供了可以连接到服务的其他服务和应用程序。例如,如果您想将所有设备发送的遥测消息存储到数据库中,通常只需在云服务提供商的配置工具中点击几下,就可以将服务连接到数据库并将数据流入其中。
## 在云端创建一个 IoT 服务
现在您已经拥有了一个 Azure 订阅,您可以注册一个 IoT 服务。微软提供的 IoT 服务叫做 Azure IoT Hub。
![Azure IoT Hub 标志](../../../../../translated_images/zh-CN/azure-iot-hub-logo.28a19de76d0a1932464d858f7558712bcdace3e5ec69c434d482ed7ce41c3a26.png)
下面的视频简要介绍了 Azure IoT Hub
[![Azure IoT Hub 概述视频](https://img.youtube.com/vi/smuZaZZXKsU/0.jpg)](https://www.youtube.com/watch?v=smuZaZZXKsU)
> 🎥 点击上方图片观看视频
✅ 花点时间研究并阅读 [Microsoft IoT Hub 文档](https://docs.microsoft.com/azure/iot-hub/about-iot-hub?WT.mc_id=academic-17441-jabenn)中的 IoT Hub 概述。
Azure 提供的云服务可以通过基于网页的门户或命令行界面CLI进行配置。在本任务中您将使用 CLI。
### 任务 - 安装 Azure CLI
要使用 Azure CLI首先需要在您的 PC 或 Mac 上安装它。
1. 按照 [Azure CLI 文档](https://docs.microsoft.com/cli/azure/install-azure-cli?WT.mc_id=academic-17441-jabenn)中的说明安装 CLI。
1. Azure CLI 支持许多扩展,这些扩展增加了管理各种 Azure 服务的功能。通过命令行或终端运行以下命令安装 IoT 扩展:
```sh
az extension add --name azure-iot
```
1. 在命令行或终端中运行以下命令,从 Azure CLI 登录到您的 Azure 订阅。
```sh
az login
```
默认浏览器将打开一个网页。使用您注册 Azure 订阅时的账户登录。登录后,您可以关闭浏览器标签页。
1. 如果您有多个 Azure 订阅,例如学校提供的订阅和您自己的 Azure for Students 订阅,您需要选择要使用的订阅。运行以下命令列出您有权限访问的所有订阅:
```sh
az account list --output table
```
输出中将显示每个订阅的名称及其 `SubscriptionId`
```output
➜ ~ az account list --output table
Name CloudName SubscriptionId State IsDefault
---------------------- ----------- ------------------------------------ ------- -----------
School-subscription AzureCloud cb30cde9-814a-42f0-a111-754cb788e4e1 Enabled True
Azure for Students AzureCloud fa51c31b-162c-4599-add6-781def2e1fbf Enabled False
```
使用以下命令选择您要使用的订阅:
```sh
az account set --subscription <SubscriptionId>
```
`<SubscriptionId>` 替换为您想使用的订阅的 ID。运行此命令后重新运行列出账户的命令。您会看到 `IsDefault` 列标记为 `True`,表示您刚刚设置的订阅。
### 任务 - 创建资源组
Azure 服务(如 IoT Hub 实例、虚拟机、数据库或 AI 服务)被称为**资源**。每个资源都必须属于一个**资源组**,即一个或多个资源的逻辑分组。
> 💁 使用资源组可以一次性管理多个服务。例如,在完成本项目的所有课程后,您可以删除资源组,资源组中的所有资源也会被自动删除。
1. Azure 在全球有多个数据中心,这些数据中心被划分为不同的区域。创建 Azure 资源或资源组时,您需要指定创建位置。运行以下命令获取位置列表:
```sh
az account list-locations --output table
```
您将看到一个位置列表。这个列表会很长。
> 💁 截至撰写本文时,您可以部署到 65 个位置。
```output
➜ ~ az account list-locations --output table
DisplayName Name RegionalDisplayName
------------------------ ------------------- -------------------------------------
East US eastus (US) East US
East US 2 eastus2 (US) East US 2
South Central US southcentralus (US) South Central US
...
```
记下离您最近的区域的 `Name` 列中的值。您可以在 [Azure 地理位置页面](https://azure.microsoft.com/global-infrastructure/geographies/?WT.mc_id=academic-17441-jabenn)上查看这些区域的地图。
1. 运行以下命令创建一个名为 `soil-moisture-sensor` 的资源组。资源组名称在您的订阅中必须是唯一的。
```sh
az group create --name soil-moisture-sensor \
--location <location>
```
`<location>` 替换为您在上一步中选择的位置。
### 任务 - 创建 IoT Hub
现在,您可以在资源组中创建一个 IoT Hub 资源。
1. 使用以下命令创建 IoT Hub 资源:
```sh
az iot hub create --resource-group soil-moisture-sensor \
--sku F1 \
--partition-count 2 \
--name <hub_name>
```
`<hub_name>` 替换为您的 Hub 名称。此名称必须在全球范围内唯一——即任何人创建的 IoT Hub 都不能有相同的名称。此名称将用于指向 Hub 的 URL因此需要唯一。可以使用类似 `soil-moisture-sensor-` 的名称,并在末尾添加一些唯一标识符,例如随机单词或您的名字。
`--sku F1` 选项表示使用免费层。免费层支持每天 8,000 条消息,并提供大多数付费层的功能。
> 🎓 Azure 服务的不同定价级别称为层。每个层的成本不同,提供的功能或数据量也不同。
> 💁 如果您想了解更多关于定价的信息,可以查看 [Azure IoT Hub 定价指南](https://azure.microsoft.com/pricing/details/iot-hub/?WT.mc_id=academic-17441-jabenn)。
`--partition-count 2` 选项定义 IoT Hub 支持的数据流数量。更多分区可以减少多个设备从 IoT Hub 读写数据时的阻塞情况。分区的详细内容超出了本课程的范围,但创建免费层 IoT Hub 时需要设置此值。
> 💁 每个订阅只能有一个免费层 IoT Hub。
IoT Hub 将被创建。完成此过程可能需要一分钟左右。
## 与 IoT Hub 通信
在上一课中,您使用 MQTT 在不同主题上发送和接收消息,不同主题有不同的用途。而在 IoT Hub 中,设备与 Hub 或 Hub 与设备之间的通信有多种定义好的方式。
> 💁 在底层IoT Hub 和设备之间的通信可以使用 MQTT、HTTPS 或 AMQP。
* 设备到云D2C消息 - 这些是从设备发送到 IoT Hub 的消息,例如遥测数据。应用程序代码可以从 IoT Hub 中读取这些消息。
> 🎓 在底层IoT Hub 使用一个名为 [Event Hubs](https://docs.microsoft.com/azure/event-hubs/?WT.mc_id=academic-17441-jabenn) 的 Azure 服务。当您编写代码读取发送到 Hub 的消息时,这些消息通常被称为事件。
* 云到设备C2D消息 - 这些是从应用程序代码通过 IoT Hub 发送到 IoT 设备的消息。
* 直接方法请求 - 这些是从应用程序代码通过 IoT Hub 发送到 IoT 设备的消息,用于请求设备执行某些操作,例如控制执行器。这些消息需要响应,以便应用程序代码知道是否成功处理。
* 设备孪生 - 这些是 JSON 文档,在设备和 IoT Hub 之间保持同步,用于存储设备报告的设置或其他属性,或者 IoT Hub 应设置在设备上的属性(称为期望值)。
IoT Hub 可以存储消息和直接方法请求一段可配置的时间(默认一天),因此如果设备或应用程序代码失去连接,重新连接后仍然可以检索离线期间发送的消息。设备孪生会永久保存在 IoT Hub 中,因此设备可以随时重新连接并获取最新的设备孪生。
✅ 做一些研究:阅读 IoT Hub 文档中的 [设备到云通信指南](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-d2c-guidance?WT.mc_id=academic-17441-jabenn) 和 [云到设备通信指南](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-c2d-guidance?WT.mc_id=academic-17441-jabenn)。
## 将设备连接到 IoT 服务
一旦 Hub 创建完成,您的 IoT 设备就可以连接到它。只有注册的设备才能连接到服务,因此您需要先注册设备。注册后,您将获得一个连接字符串,设备可以使用它进行连接。这个连接字符串是设备特定的,包含有关 IoT Hub、设备和允许设备连接的密钥的信息。
> 🎓 连接字符串是一个通用术语,指包含连接详细信息的一段文本。这些字符串用于连接 IoT Hub、数据库和许多其他服务。它们通常包括服务的标识符如 URL和安全信息如密钥。这些字符串会传递给 SDK 以连接到服务。
> ⚠️ 连接字符串应保持安全!安全性将在后续课程中详细介绍。
### 任务 - 注册您的 IoT 设备
可以使用 Azure CLI 将 IoT 设备注册到 IoT Hub。
1. 运行以下命令注册设备:
```sh
az iot hub device-identity create --device-id soil-moisture-sensor \
--hub-name <hub_name>
```
`<hub_name>` 替换为您为 IoT Hub 使用的名称。
这将创建一个 ID 为 `soil-moisture-sensor` 的设备。
1. 当您的 IoT 设备使用 SDK 连接到 IoT Hub 时,需要使用一个连接字符串,该字符串包含 Hub 的 URL 和密钥。运行以下命令获取连接字符串:
```sh
az iot hub device-identity connection-string show --device-id soil-moisture-sensor \
--output table \
--hub-name <hub_name>
```
`<hub_name>` 替换为您为 IoT Hub 使用的名称。
1. 保存输出中显示的连接字符串,稍后您将需要它。
### 任务 - 将 IoT 设备连接到云
按照相关指南将您的 IoT 设备连接到云:
* [Arduino - Wio Terminal](wio-terminal-connect-hub.md)
* [单板计算机 - Raspberry Pi/虚拟 IoT 设备](single-board-computer-connect-hub.md)
### 任务 - 监控事件
目前,您无需更新服务器代码。您可以使用 Azure CLI 监控来自 IoT 设备的事件。
1. 确保您的 IoT 设备正在运行并发送土壤湿度遥测值。
1. 在命令提示符或终端中运行以下命令以监控发送到 IoT Hub 的消息:
```sh
az iot hub monitor-events --hub-name <hub_name>
```
`<hub_name>` 替换为您为 IoT Hub 使用的名称。
您将在控制台输出中看到 IoT 设备发送的消息。
```output
Starting event monitor, use ctrl-c to stop...
{
"event": {
"origin": "soil-moisture-sensor",
"module": "",
"interface": "",
"component": "",
"payload": "{\"soil_moisture\": 376}"
}
},
{
"event": {
"origin": "soil-moisture-sensor",
"module": "",
"interface": "",
"component": "",
"payload": "{\"soil_moisture\": 381}"
}
}
```
`payload` 的内容将与您的 IoT 设备发送的消息匹配。
> 截至撰写本文时,`az iot` 扩展在 Apple Silicon 上尚未完全工作。如果您使用的是 Apple Silicon 设备,则需要通过其他方式监控消息,例如使用 [Visual Studio Code 的 Azure IoT 工具](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-vscode-iot-toolkit-cloud-device-messaging)。
1. 这些消息会自动附加一些属性,例如发送的时间戳。这些属性称为*注解*。要查看所有消息注解,请使用以下命令:
```sh
az iot hub monitor-events --properties anno --hub-name <hub_name>
```
`<hub_name>` 替换为您为 IoT Hub 使用的名称。
您将在控制台输出中看到 IoT 设备发送的消息。
```output
Starting event monitor, use ctrl-c to stop...
{
"event": {
"origin": "soil-moisture-sensor",
"module": "",
"interface": "",
"component": "",
"properties": {},
"annotations": {
"iothub-connection-device-id": "soil-moisture-sensor",
"iothub-connection-auth-method": "{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}",
"iothub-connection-auth-generation-id": "637553997165220462",
"iothub-enqueuedtime": 1619976150288,
"iothub-message-source": "Telemetry",
"x-opt-sequence-number": 1379,
"x-opt-offset": "550576",
"x-opt-enqueued-time": 1619976150277
},
"payload": "{\"soil_moisture\": 381}"
}
}
```
注解中的时间值是 [UNIX 时间](https://wikipedia.org/wiki/Unix_time),表示自 1970 年 1 月 1 日午夜以来的秒数。
完成后退出事件监控。
### 任务 - 控制您的 IoT 设备
您还可以使用 Azure CLI 调用 IoT 设备上的直接方法。
1. 在命令提示符或终端中运行以下命令以调用 IoT 设备上的 `relay_on` 方法:
```sh
az iot hub invoke-device-method --device-id soil-moisture-sensor \
--method-name relay_on \
--method-payload '{}' \
--hub-name <hub_name>
```
将 `
<hub_name>
使用您为 IoT Hub 设置的名称。
这将发送一个直接方法请求,调用指定的 `method-name` 方法。直接方法可以携带一个包含方法数据的有效负载,这可以通过 `method-payload` 参数以 JSON 格式指定。
您将看到继电器打开,并从您的 IoT 设备中看到相应的输出:
```output
Direct method received - relay_on
```
1. 重复上述步骤,但将 `--method-name` 设置为 `relay_off`。您将看到继电器关闭,并从 IoT 设备中看到相应的输出。
---
## 🚀 挑战
IoT Hub 的免费层每天允许发送 8,000 条消息。您编写的代码每 10 秒发送一条遥测消息。每 10 秒发送一条消息,一天会发送多少条消息?
思考一下,土壤湿度的测量数据应该多频繁发送?如何修改您的代码以保持在免费层的限制内,同时确保需要时进行检查但不过于频繁?如果您想添加第二个设备,该怎么办?
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/16)
## 复习与自学
IoT Hub SDK 对 Arduino 和 Python 都是开源的。在 GitHub 上的代码仓库中,有许多示例展示了如何使用不同的 IoT Hub 功能。
* 如果您使用的是 Wio Terminal请查看 [GitHub 上的 Arduino 示例](https://github.com/Azure/azure-iot-pal-arduino/tree/master/pal/samples)
* 如果您使用的是 Raspberry Pi 或虚拟设备,请查看 [GitHub 上的 Python 示例](https://github.com/Azure/azure-iot-sdk-python/tree/master/azure-iot-hub/samples)
## 作业
[了解云服务](assignment.md)
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,22 @@
# 了解云服务
## 说明
云服务,例如微软的 Azure不仅仅是提供可租用的计算资源。主要的云服务类型包括
* 基础设施即服务 (IaaS)
* 平台即服务 (PaaS)
* 无服务器架构
* 软件即服务 (SaaS)
了解这些不同类型的服务,解释它们是什么以及它们之间的区别。说明哪些服务与物联网开发者相关。
## 评分标准
| 标准 | 卓越 | 合格 | 需要改进 |
| -------- | --------- | -------- | ----------------- |
| 解释不同的云服务类型 | 清晰地解释了所有 4 种服务类型 | 能够解释 3 种服务类型 | 只能解释 1 或 2 种服务类型 |
| 解释哪些服务与物联网相关 | 描述了哪些服务与物联网开发者相关以及原因 | 描述了哪些服务与物联网开发者相关但未说明原因 | 无法描述哪些服务与物联网开发者相关 |
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,119 @@
# 将您的 IoT 设备连接到云端 - 虚拟 IoT 硬件和 Raspberry Pi
在本课程的这一部分,您将把虚拟 IoT 设备或 Raspberry Pi 连接到您的 IoT Hub以发送遥测数据并接收命令。
## 将设备连接到 IoT Hub
下一步是将您的设备连接到 IoT Hub。
### 任务 - 连接到 IoT Hub
1. 在 VS Code 中打开 `soil-moisture-sensor` 文件夹。如果您使用的是虚拟 IoT 设备,请确保虚拟环境在终端中运行。
1. 安装一些额外的 Pip 包:
```sh
pip3 install azure-iot-device
```
`azure-iot-device` 是一个用于与 IoT Hub 通信的库。
1. 在 `app.py` 文件顶部的现有导入语句下方添加以下导入:
```python
from azure.iot.device import IoTHubDeviceClient, Message, MethodResponse
```
这段代码导入了与 IoT Hub 通信的 SDK。
1. 删除 `import paho.mqtt.client as mqtt` 这一行,因为这个库不再需要。删除所有 MQTT 相关代码,包括主题名称、所有使用 `mqtt_client` 的代码以及 `handle_command`。保留 `while True:` 循环,只需删除该循环中的 `mqtt_client.publish` 行即可。
1. 在导入语句下方添加以下代码:
```python
connection_string = "<connection string>"
```
`<connection string>` 替换为您在本课程前面为设备检索到的连接字符串。
> 💁 这不是最佳实践。连接字符串不应该存储在源代码中,因为它可能会被提交到源代码控制中并被任何人发现。我们在这里这样做是为了简化操作。理想情况下,您应该使用环境变量和类似 [`python-dotenv`](https://pypi.org/project/python-dotenv/) 的工具。您将在后续课程中了解更多相关内容。
1. 在这段代码下方添加以下内容,以创建一个可以与 IoT Hub 通信的设备客户端对象并连接它:
```python
device_client = IoTHubDeviceClient.create_from_connection_string(connection_string)
print('Connecting')
device_client.connect()
print('Connected')
```
1. 运行这段代码。您将看到您的设备已连接。
```output
pi@raspberrypi:~/soil-moisture-sensor $ python3 app.py
Connecting
Connected
Soil moisture: 379
```
## 发送遥测数据
现在您的设备已连接,您可以将遥测数据发送到 IoT Hub而不是发送到 MQTT broker。
### 任务 - 发送遥测数据
1. 在 `while True` 循环中添加以下代码,就在 `sleep` 之前:
```python
message = Message(json.dumps({ 'soil_moisture': soil_moisture }))
device_client.send_message(message)
```
这段代码创建了一个包含土壤湿度读数的 JSON 字符串的 IoT Hub `Message`,然后将其作为设备到云端消息发送到 IoT Hub。
## 处理命令
您的设备需要处理来自服务器代码的命令以控制继电器。这是通过直接方法请求发送的。
### 任务 - 处理直接方法请求
1. 在 `while True` 循环之前添加以下代码:
```python
def handle_method_request(request):
print("Direct method received - ", request.name)
if request.name == "relay_on":
relay.on()
elif request.name == "relay_off":
relay.off()
```
这定义了一个方法 `handle_method_request`,当 IoT Hub 调用直接方法时会调用该方法。每个直接方法都有一个名称,这段代码期望一个名为 `relay_on` 的方法来打开继电器,以及一个名为 `relay_off` 的方法来关闭继电器。
> 💁 这也可以通过一个单一的直接方法请求来实现,将继电器的期望状态作为一个有效负载传递,该有效负载可以通过方法请求传递并从 `request` 对象中获取。
1. 直接方法需要一个响应来告诉调用代码请求已被处理。在 `handle_method_request` 函数的末尾添加以下代码,以创建对请求的响应:
```python
method_response = MethodResponse.create_from_method_request(request, 200)
device_client.send_method_response(method_response)
```
这段代码向直接方法请求发送一个 HTTP 状态码为 200 的响应,并将其返回到 IoT Hub。
1. 在该函数定义下方添加以下代码:
```python
device_client.on_method_request_received = handle_method_request
```
这段代码告诉 IoT Hub 客户端在调用直接方法时调用 `handle_method_request` 函数。
> 💁 您可以在 [code/pi](../../../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/pi) 或 [code/virtual-device](../../../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/virtual-device) 文件夹中找到这段代码。
😀 您的土壤湿度传感器程序已连接到您的 IoT Hub
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,295 @@
# 将您的 IoT 设备连接到云端 - Wio Terminal
在本课程的这一部分,您将把 Wio Terminal 连接到 IoT Hub以发送遥测数据并接收命令。
## 将设备连接到 IoT Hub
下一步是将您的设备连接到 IoT Hub。
### 任务 - 连接到 IoT Hub
1. 在 VS Code 中打开 `soil-moisture-sensor` 项目。
1. 打开 `platformio.ini` 文件。移除 `knolleary/PubSubClient` 库依赖项。该库用于连接到公共 MQTT 代理,但连接到 IoT Hub时不需要。
1. 添加以下库依赖项:
```ini
seeed-studio/Seeed Arduino RTC @ 2.0.0
arduino-libraries/AzureIoTHub @ 1.6.0
azure/AzureIoTUtility @ 1.6.1
azure/AzureIoTProtocol_MQTT @ 1.6.0
azure/AzureIoTProtocol_HTTP @ 1.6.0
azure/AzureIoTSocket_WiFi @ 1.0.2
```
`Seeed Arduino RTC` 库提供与 Wio Terminal 的实时时钟交互的代码,用于跟踪时间。其余库允许您的 IoT 设备连接到 IoT Hub。
1. 在 `platformio.ini` 文件底部添加以下内容:
```ini
build_flags =
-DDONT_USE_UPLOADTOBLOB
```
这设置了一个编译器标志,在编译 Arduino IoT Hub 代码时需要。
1. 打开 `config.h` 头文件。移除所有 MQTT 设置,并添加以下设备连接字符串常量:
```cpp
// IoT Hub settings
const char *CONNECTION_STRING = "<connection string>";
```
`<connection string>` 替换为您之前复制的设备连接字符串。
1. 连接到 IoT Hub 使用基于时间的令牌。这意味着 IoT 设备需要知道当前时间。与 Windows、macOS 或 Linux 等操作系统不同,微控制器不会自动通过互联网同步当前时间。这意味着您需要添加代码从 [NTP](https://wikipedia.org/wiki/Network_Time_Protocol) 服务器获取当前时间。一旦时间被检索,可以将其存储在 Wio Terminal 的实时时钟中,允许在设备未断电的情况下稍后请求正确时间。添加一个名为 `ntp.h` 的新文件,并包含以下代码:
```cpp
#pragma once
#include "DateTime.h"
#include <time.h>
#include "samd/NTPClientAz.h"
#include <sys/time.h>
static void initTime()
{
WiFiUDP _udp;
time_t epochTime = (time_t)-1;
NTPClientAz ntpClient;
ntpClient.begin();
while (true)
{
epochTime = ntpClient.getEpochTime("0.pool.ntp.org");
if (epochTime == (time_t)-1)
{
Serial.println("Fetching NTP epoch time failed! Waiting 2 seconds to retry.");
delay(2000);
}
else
{
Serial.print("Fetched NTP epoch time is: ");
char buff[32];
sprintf(buff, "%.f", difftime(epochTime, (time_t)0));
Serial.println(buff);
break;
}
}
ntpClient.end();
struct timeval tv;
tv.tv_sec = epochTime;
tv.tv_usec = 0;
settimeofday(&tv, NULL);
}
```
这些代码的细节超出了本课程的范围。它定义了一个名为 `initTime` 的函数,该函数从 NTP 服务器获取当前时间并用它设置 Wio Terminal 的时钟。
1. 打开 `main.cpp` 文件,移除所有 MQTT 代码,包括 `PubSubClient.h` 头文件、`PubSubClient` 变量的声明、`reconnectMQTTClient` 和 `createMQTTClient` 方法,以及对这些变量和方法的任何调用。该文件应仅包含连接到 WiFi、获取土壤湿度并创建 JSON 文档的代码。
1. 在 `main.cpp` 文件顶部添加以下 `#include` 指令,以包含 IoT Hub 库和设置时间的头文件:
```cpp
#include <AzureIoTHub.h>
#include <AzureIoTProtocol_MQTT.h>
#include <iothubtransportmqtt.h>
#include "ntp.h"
```
1. 在 `setup` 函数末尾添加以下调用以设置当前时间:
```cpp
initTime();
```
1. 在文件顶部的 `#include` 指令下方添加以下变量声明:
```cpp
IOTHUB_DEVICE_CLIENT_LL_HANDLE _device_ll_handle;
```
这声明了一个 `IOTHUB_DEVICE_CLIENT_LL_HANDLE`,用于连接到 IoT Hub 的句柄。
1. 在此下方添加以下代码:
```cpp
static void connectionStatusCallback(IOTHUB_CLIENT_CONNECTION_STATUS result, IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, void *user_context)
{
if (result == IOTHUB_CLIENT_CONNECTION_AUTHENTICATED)
{
Serial.println("The device client is connected to iothub");
}
else
{
Serial.println("The device client has been disconnected");
}
}
```
这声明了一个回调函数,当连接到 IoT Hub 的状态发生变化(例如连接或断开连接)时会被调用。状态会发送到串口。
1. 在此下方添加一个函数以连接到 IoT Hub
```cpp
void connectIoTHub()
{
IoTHub_Init();
_device_ll_handle = IoTHubDeviceClient_LL_CreateFromConnectionString(CONNECTION_STRING, MQTT_Protocol);
if (_device_ll_handle == NULL)
{
Serial.println("Failure creating Iothub device. Hint: Check your connection string.");
return;
}
IoTHubDeviceClient_LL_SetConnectionStatusCallback(_device_ll_handle, connectionStatusCallback, NULL);
}
```
此代码初始化 IoT Hub 库代码,然后使用 `config.h` 头文件中的连接字符串创建连接。此连接基于 MQTT。如果连接失败错误信息会发送到串口——如果您在输出中看到此信息请检查连接字符串。最后设置连接状态回调。
1. 在 `setup` 函数中调用此函数,位置在 `initTime` 调用下方:
```cpp
connectIoTHub();
```
1. 与 MQTT 客户端类似,此代码运行在单线程上,因此需要时间处理由 Hub 发送和发送到 Hub 的消息。在 `loop` 函数顶部添加以下内容以实现此功能:
```cpp
IoTHubDeviceClient_LL_DoWork(_device_ll_handle);
```
1. 构建并上传此代码。您将在串口监视器中看到连接:
```output
Connecting to WiFi..
Connected!
Fetched NTP epoch time is: 1619983687
Sending telemetry {"soil_moisture":391}
The device client is connected to iothub
```
在输出中,您可以看到 NTP 时间被获取,然后设备客户端连接。连接可能需要几秒钟,因此在设备连接时,您可能会在输出中看到土壤湿度。
> 💁 您可以使用像 [unixtimestamp.com](https://www.unixtimestamp.com) 这样的网站将 NTP 的 UNIX 时间转换为更易读的格式。
## 发送遥测数据
现在您的设备已连接,您可以将遥测数据发送到 IoT Hub而不是 MQTT 代理。
### 任务 - 发送遥测数据
1. 在 `setup` 函数上方添加以下函数:
```cpp
void sendTelemetry(const char *telemetry)
{
IOTHUB_MESSAGE_HANDLE message_handle = IoTHubMessage_CreateFromString(telemetry);
IoTHubDeviceClient_LL_SendEventAsync(_device_ll_handle, message_handle, NULL, NULL);
IoTHubMessage_Destroy(message_handle);
}
```
此代码从传递的字符串参数创建一个 IoT Hub 消息,将其发送到 Hub然后清理消息对象。
1. 在 `loop` 函数中调用此代码,位置在将遥测数据发送到串口的行之后:
```cpp
sendTelemetry(telemetry.c_str());
```
## 处理命令
您的设备需要处理来自服务器代码的命令以控制继电器。这是通过直接方法请求发送的。
## 任务 - 处理直接方法请求
1. 在 `connectIoTHub` 函数之前添加以下代码:
```cpp
int directMethodCallback(const char *method_name, const unsigned char *payload, size_t size, unsigned char **response, size_t *response_size, void *userContextCallback)
{
Serial.printf("Direct method received %s\r\n", method_name);
if (strcmp(method_name, "relay_on") == 0)
{
digitalWrite(PIN_WIRE_SCL, HIGH);
}
else if (strcmp(method_name, "relay_off") == 0)
{
digitalWrite(PIN_WIRE_SCL, LOW);
}
}
```
此代码定义了一个回调方法,当 IoT Hub 库收到直接方法请求时会调用该方法。请求的方法通过 `method_name` 参数传递。此函数将调用的方法打印到串口,然后根据方法名称打开或关闭继电器。
> 💁 这也可以通过单个直接方法请求实现,将继电器的期望状态作为有效负载传递,该有效负载可以通过方法请求传递并从 `payload` 参数中获取。
1. 在 `directMethodCallback` 函数末尾添加以下代码:
```cpp
char resultBuff[16];
sprintf(resultBuff, "{\"Result\":\"\"}");
*response_size = strlen(resultBuff);
*response = (unsigned char *)malloc(*response_size);
memcpy(*response, resultBuff, *response_size);
return IOTHUB_CLIENT_OK;
```
直接方法请求需要响应,响应分为两部分——文本响应和返回码。此代码将创建以下 JSON 文档作为结果:
```JSON
{
"Result": ""
}
```
然后将其复制到 `response` 参数中,并在 `response_size` 参数中设置响应的大小。此代码随后返回 `IOTHUB_CLIENT_OK`,表示方法已正确处理。
1. 在 `connectIoTHub` 函数末尾添加以下代码以连接回调:
```cpp
IoTHubClient_LL_SetDeviceMethodCallback(_device_ll_handle, directMethodCallback, NULL);
```
1. `loop` 函数将调用 `IoTHubDeviceClient_LL_DoWork` 函数以处理 IoT Hub 发送的事件。由于 `delay` 的原因,这仅每 10 秒调用一次,这意味着直接方法每 10 秒才会处理一次。为了提高效率,可以将 10 秒的延迟分成多个较短的延迟,每次调用 `IoTHubDeviceClient_LL_DoWork`。为此,在 `loop` 函数上方添加以下代码:
```cpp
void work_delay(int delay_time)
{
int current = 0;
do
{
IoTHubDeviceClient_LL_DoWork(_device_ll_handle);
delay(100);
current += 100;
} while (current < delay_time);
}
```
此代码将重复循环,调用 `IoTHubDeviceClient_LL_DoWork` 并每次延迟 100 毫秒。它会根据 `delay_time` 参数的值重复多次。这意味着设备最多等待 100 毫秒来处理直接方法请求。
1. 在 `loop` 函数中,移除对 `IoTHubDeviceClient_LL_DoWork` 的调用,并将 `delay(10000)` 替换为以下代码以调用新函数:
```cpp
work_delay(10000);
```
> 💁 您可以在 [code/wio-terminal](../../../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud/code/wio-terminal) 文件夹中找到此代码。
😀 您的土壤湿度传感器程序已连接到 IoT Hub
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而产生的任何误解或误读不承担责任。

@ -0,0 +1,645 @@
# 将应用程序逻辑迁移到云端
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-9.dfe99c8e891f48e179724520da9f5794392cf9a625079281ccdcbf09bd85e1b6.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看更大版本。
本课程是 [Microsoft Reactor](https://developer.microsoft.com/reactor/?WT.mc_id=academic-17441-jabenn) 的 [IoT 初学者项目 2 - 数字农业系列](https://youtube.com/playlist?list=PLmsFUfdnGr3yCutmcVg6eAUEfsGiFXgcx) 的一部分。
[![使用无服务器代码控制您的 IoT 设备](https://img.youtube.com/vi/VVZDcs5u1_I/0.jpg)](https://youtu.be/VVZDcs5u1_I)
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/17)
## 简介
在上一课中,您学习了如何将植物土壤湿度监测和继电器控制连接到基于云的 IoT 服务。下一步是将控制继电器定时的服务器代码迁移到云端。在本课中,您将学习如何使用无服务器函数来完成这一任务。
本课内容包括:
* [什么是无服务器?](../../../../../2-farm/lessons/5-migrate-application-to-the-cloud)
* [创建无服务器应用程序](../../../../../2-farm/lessons/5-migrate-application-to-the-cloud)
* [创建 IoT Hub 事件触发器](../../../../../2-farm/lessons/5-migrate-application-to-the-cloud)
* [从无服务器代码发送直接方法请求](../../../../../2-farm/lessons/5-migrate-application-to-the-cloud)
* [将无服务器代码部署到云端](../../../../../2-farm/lessons/5-migrate-application-to-the-cloud)
## 什么是无服务器?
无服务器(或无服务器计算)是指创建在云端运行的小型代码块,这些代码块会根据不同类型的事件触发运行。当事件发生时,您的代码会运行,并接收有关事件的数据。这些事件可以来自许多不同的来源,包括网络请求、队列中的消息、数据库中的数据变化,或 IoT 设备发送到 IoT 服务的消息。
![事件从 IoT 服务发送到无服务器服务,同时由多个函数处理](../../../../../translated_images/zh-CN/iot-messages-to-serverless.0194da1cc0732bb7d0f823aed3fce54735c6b1ad3bf36089804d8aaefc0a774f.png)
> 💁 如果您之前使用过数据库触发器,可以将其类比为代码因事件(如插入一行数据)而触发。
![当许多事件同时发送时,无服务器服务会扩展以同时运行所有事件](../../../../../translated_images/zh-CN/serverless-scaling.f8c769adf0413fd1.webp)
您的代码仅在事件发生时运行,其他时间不会保持活动状态。事件发生时,代码会被加载并运行。这使得无服务器非常具有扩展性——如果许多事件同时发生,云提供商可以根据需要同时运行您的函数,利用其可用的服务器资源。缺点是,如果需要在事件之间共享信息,必须将其存储在数据库等地方,而不能存储在内存中。
您的代码以函数的形式编写,函数会将事件的详细信息作为参数传递。您可以使用多种编程语言来编写这些无服务器函数。
> 🎓 无服务器也被称为函数即服务FaaS因为每个事件触发器都在代码中实现为一个函数。
尽管名字叫“无服务器”,但实际上还是使用了服务器。之所以命名为“无服务器”,是因为作为开发者,您无需关心运行代码所需的服务器,只需关心代码是否因事件而运行。云提供商有一个无服务器*运行时*负责管理服务器分配、网络、存储、CPU、内存以及运行代码所需的一切。这种模式意味着您无法按服务器付费而是按代码运行的时间和使用的内存付费。
> 💰 无服务器是运行云端代码最经济的方式之一。例如,在撰写本文时,一家云提供商允许您的所有无服务器函数每月总共执行 1,000,000 次,超过后每 1,000,000 次执行收费 0.20 美元。当您的代码未运行时,您无需支付费用。
作为 IoT 开发者,无服务器模型非常理想。您可以编写一个函数,响应任何连接到云托管 IoT 服务的 IoT 设备发送的消息。您的代码将处理所有发送的消息,但仅在需要时运行。
✅ 回顾您编写的服务器代码,该代码通过 MQTT 监听消息。如何在云端使用无服务器运行这些代码?您认为代码需要做哪些修改以支持无服务器计算?
> 💁 无服务器模型正在扩展到其他云服务,不仅限于运行代码。例如,云端的无服务器数据库使用无服务器定价模型,按对数据库的请求(如查询或插入)收费,通常基于完成请求所需的工作量定价。例如,针对主键选择一行的简单查询成本低于连接多个表并返回数千行的复杂操作。
## 创建无服务器应用程序
Microsoft 的无服务器计算服务称为 Azure Functions。
![Azure Functions 标志](../../../../../translated_images/zh-CN/azure-functions-logo.1cfc8e3204c9c44aaf80fcf406fc8544d80d7f00f8d3e8ed6fed764563e17564.png)
以下短视频概述了 Azure Functions
[![Azure Functions 概述视频](https://img.youtube.com/vi/8-jz5f_JyEQ/0.jpg)](https://www.youtube.com/watch?v=8-jz5f_JyEQ)
> 🎥 点击上方图片观看视频
✅ 花点时间研究并阅读 [Microsoft Azure Functions 文档](https://docs.microsoft.com/azure/azure-functions/functions-overview?WT.mc_id=academic-17441-jabenn) 中的 Azure Functions 概述。
要编写 Azure Functions您需要选择一种编程语言并创建 Azure Functions 应用程序。Azure Functions 原生支持 Python、JavaScript、TypeScript、C#、F#、Java 和 Powershell。在本课中您将学习如何使用 Python 编写 Azure Functions 应用程序。
> 💁 Azure Functions 还支持自定义处理程序,因此您可以使用任何支持 HTTP 请求的语言编写函数,包括较旧的语言如 COBOL。
Functions 应用程序由一个或多个*触发器*组成——响应事件的函数。一个 Functions 应用程序可以包含多个触发器,共享通用配置。例如,您的 Functions 应用程序的配置文件可以包含 IoT Hub 的连接详细信息,应用程序中的所有函数都可以使用这些信息来连接并监听事件。
### 任务 - 安装 Azure Functions 工具
> 在撰写本文时Azure Functions 的代码工具在 Apple Silicon 上的 Python 项目中尚未完全工作。您需要使用基于 Intel 的 Mac、Windows PC 或 Linux PC。
Azure Functions 的一个重要功能是可以在本地运行。云端使用的运行时可以在您的计算机上运行,允许您编写响应 IoT 消息的代码并在本地运行。您甚至可以在处理事件时调试代码。一旦对代码满意,可以将其部署到云端。
Azure Functions 工具以 CLI 的形式提供,称为 Azure Functions Core Tools。
1. 按照 [Azure Functions Core Tools 文档](https://docs.microsoft.com/azure/azure-functions/functions-run-local?WT.mc_id=academic-17441-jabenn) 中的说明安装 Azure Functions Core Tools。
1. 安装 VS Code 的 Azure Functions 扩展。此扩展提供创建、调试和部署 Azure Functions 的支持。请参考 [Azure Functions 扩展文档](https://marketplace.visualstudio.com/items?WT.mc_id=academic-17441-jabenn&itemName=ms-azuretools.vscode-azurefunctions) 了解如何在 VS Code 中安装此扩展。
当您将 Azure Functions 应用程序部署到云端时,它需要使用少量云存储来存储应用程序文件和日志文件等内容。当您在本地运行 Functions 应用程序时,仍需连接到云存储,但可以使用名为 [Azurite](https://github.com/Azure/Azurite) 的存储模拟器代替实际云存储。它在本地运行,但行为类似于云存储。
> 🎓 在 Azure 中Azure Functions 使用的存储是 Azure Storage Account。这些账户可以存储文件、Blob、表中的数据或队列中的数据。您可以在多个应用程序之间共享一个存储账户例如 Functions 应用程序和 Web 应用程序。
1. Azurite 是一个 Node.js 应用程序,因此您需要安装 Node.js。您可以在 [Node.js 网站](https://nodejs.org/) 上找到下载和安装说明。如果您使用 Mac也可以通过 [Homebrew](https://formulae.brew.sh/formula/node) 安装。
1. 使用以下命令安装 Azurite`npm` 是安装 Node.js 时附带的工具):
```sh
npm install -g azurite
```
1. 创建一个名为 `azurite` 的文件夹供 Azurite 存储数据:
```sh
mkdir azurite
```
1. 运行 Azurite并传递此新文件夹
```sh
azurite --location azurite
```
Azurite 存储模拟器将启动并准备好供本地 Functions 运行时连接。
```output
➜ ~ azurite --location azurite
Azurite Blob service is starting at http://127.0.0.1:10000
Azurite Blob service is successfully listening at http://127.0.0.1:10000
Azurite Queue service is starting at http://127.0.0.1:10001
Azurite Queue service is successfully listening at http://127.0.0.1:10001
Azurite Table service is starting at http://127.0.0.1:10002
Azurite Table service is successfully listening at http://127.0.0.1:10002
```
### 任务 - 创建 Azure Functions 项目
Azure Functions CLI 可用于创建新的 Functions 应用程序。
1. 创建一个文件夹用于存放 Functions 应用程序,并导航到该文件夹。命名为 `soil-moisture-trigger`
```sh
mkdir soil-moisture-trigger
cd soil-moisture-trigger
```
1. 在此文件夹中创建一个 Python 虚拟环境:
```sh
python3 -m venv .venv
```
1. 激活虚拟环境:
* 在 Windows 上:
* 如果使用命令提示符或通过 Windows Terminal 使用命令提示符,运行:
```cmd
.venv\Scripts\activate.bat
```
* 如果使用 PowerShell运行
```powershell
.\.venv\Scripts\Activate.ps1
```
* 在 macOS 或 Linux 上,运行:
```cmd
source ./.venv/bin/activate
```
> 💁 这些命令应在您创建虚拟环境的同一位置运行。您无需导航到 `.venv` 文件夹,只需在创建虚拟环境的文件夹中运行激活命令以及安装包或运行代码的命令。
1. 运行以下命令在此文件夹中创建 Functions 应用程序:
```sh
func init --worker-runtime python soil-moisture-trigger
```
这将在当前文件夹中创建三个文件:
* `host.json` - 此 JSON 文档包含 Functions 应用程序的设置。您无需修改这些设置。
* `local.settings.json` - 此 JSON 文档包含应用程序在本地运行时使用的设置,例如 IoT Hub 的连接字符串。这些设置仅用于本地,不应添加到源代码控制中。当您将应用程序部署到云端时,这些设置不会被部署,而是从应用程序设置中加载。这将在本课后面介绍。
* `requirements.txt` - 这是一个 [Pip requirements 文件](https://pip.pypa.io/en/stable/user_guide/#requirements-files),包含运行 Functions 应用程序所需的 Pip 包。
1. `local.settings.json` 文件中有一个设置用于 Functions 应用程序使用的存储账户。默认值为空,需要设置。要连接到 Azurite 本地存储模拟器,请将此值设置为以下内容:
```json
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
```
1. 使用 requirements 文件安装必要的 Pip 包:
```sh
pip install -r requirements.txt
```
> 💁 必需的 Pip 包需要在此文件中,以便在将 Functions 应用程序部署到云端时,运行时可以确保安装正确的包。
1. 为测试一切是否正常运行,可以启动 Functions 运行时。运行以下命令启动:
```sh
func start
```
您将看到运行时启动并报告未找到任何作业函数(触发器)。
```output
(.venv) ➜ soil-moisture-trigger func start
Found Python version 3.9.1 (python3).
Azure Functions Core Tools
Core Tools Version: 3.0.3442 Commit hash: 6bfab24b2743f8421475d996402c398d2fe4a9e0 (64-bit)
Function Runtime Version: 3.0.15417.0
[2021-05-05T01:24:46.795Z] No job functions found.
```
> ⚠️ 如果收到防火墙通知,请授予访问权限,因为 `func` 应用需要能够读取和写入您的网络。
> ⚠️ 如果您使用的是 macOS输出中可能会出现警告
>
> ```output
> (.venv) ➜ soil-moisture-trigger func start
> Found Python version 3.9.1 (python3).
>
> Azure Functions Core Tools
> Core Tools Version: 3.0.3442 Commit hash: 6bfab24b2743f8421475d996402c398d2fe4a9e0 (64-bit)
> Function Runtime Version: 3.0.15417.0
>
> [2021-06-16T08:18:28.315Z] Cannot create directory for shared memory usage: /dev/shm/AzureFunctions
> [2021-06-16T08:18:28.316Z] System.IO.FileSystem: Access to the path '/dev/shm/AzureFunctions' is denied. Operation not permitted.
> [2021-06-16T08:18:30.361Z] No job functions found.
> ```
>
> 只要 Functions 应用程序能够正确启动并列出正在运行的函数,您可以忽略这些警告。如 [Microsoft Docs Q&A 中的这个问题](https://docs.microsoft.com/answers/questions/396617/azure-functions-core-tools-error-osx-devshmazurefu.html?WT.mc_id=academic-17441-jabenn) 所述,这些警告可以忽略。
1. 按 `ctrl+c` 停止 Functions 应用程序。
1. 在 VS Code 中打开当前文件夹,可以通过打开 VS Code 后再打开此文件夹,或者运行以下命令:
```sh
code .
```
VS Code 会检测到您的 Functions 项目,并显示一条通知:
```output
Detected an Azure Functions Project in folder "soil-moisture-trigger" that may have been created outside of
VS Code. Initialize for optimal use with VS Code?
```
![通知](../../../../../translated_images/zh-CN/vscode-azure-functions-init-notification.bd19b49229963edb.webp)
在通知中选择 **Yes**
1. 确保 Python 虚拟环境在 VS Code 终端中运行。如果需要,可以终止并重新启动。
## 创建 IoT Hub 事件触发器
Functions 应用程序是无服务器代码的外壳。为了响应 IoT Hub 的事件,您可以向此应用程序添加一个 IoT Hub 触发器。此触发器需要连接到发送到 IoT Hub 的消息流并对其进行响应。为了获取这些消息流,您的触发器需要连接到 IoT Hub 的 *事件中心兼容端点*
IoT Hub 基于另一个 Azure 服务——Azure Event Hubs。Event Hubs 是一个允许发送和接收消息的服务,而 IoT Hub 扩展了它以添加 IoT 设备的功能。连接到 IoT Hub 读取消息的方式与使用 Event Hubs 的方式相同。
✅ 做一些研究:阅读 [Azure Event Hubs 文档](https://docs.microsoft.com/azure/event-hubs/event-hubs-about?WT.mc_id=academic-17441-jabenn) 中的概述。基本功能与 IoT Hub 有何不同?
为了让 IoT 设备连接到 IoT Hub它需要使用一个密钥来确保只有允许的设备可以连接。同样当连接以读取消息时您的代码需要一个包含密钥的连接字符串以及 IoT Hub 的详细信息。
> 💁 默认的连接字符串具有 **iothubowner** 权限,这使得任何使用它的代码都可以完全访问 IoT Hub。理想情况下您应该使用最低权限级别进行连接。这将在下一课中介绍。
一旦触发器连接成功,函数中的代码将会对发送到 IoT Hub 的每条消息进行调用,无论消息来自哪个设备。触发器会将消息作为参数传递。
### 任务 - 获取事件中心兼容端点的连接字符串
1. 在 VS Code 终端中运行以下命令以获取 IoT Hub 的事件中心兼容端点的连接字符串:
```sh
az iot hub connection-string show --default-eventhub \
--output table \
--hub-name <hub_name>
```
`<hub_name>` 替换为您为 IoT Hub 使用的名称。
1. 在 VS Code 中打开 `local.settings.json` 文件。在 `Values` 部分中添加以下值:
```json
"IOT_HUB_CONNECTION_STRING": "<connection string>"
```
`<connection string>` 替换为上一步中的值。您需要在上一行后添加一个逗号以使其成为有效的 JSON。
### 任务 - 创建事件触发器
现在您可以创建事件触发器了。
1. 在 VS Code 终端中,从 `soil-moisture-trigger` 文件夹中运行以下命令:
```sh
func new --name iot-hub-trigger --template "Azure Event Hub trigger"
```
这将创建一个名为 `iot-hub-trigger` 的新函数。触发器将连接到 IoT Hub 的事件中心兼容端点,因此您可以使用事件中心触发器。没有特定的 IoT Hub 触发器。
这将在 `soil-moisture-trigger` 文件夹中创建一个名为 `iot-hub-trigger` 的文件夹,其中包含此函数。此文件夹将包含以下文件:
* `__init__.py` - 这是包含触发器的 Python 代码文件,使用标准的 Python 文件命名约定将此文件夹转换为 Python 模块。
此文件将包含以下代码:
```python
import logging
import azure.functions as func
def main(event: func.EventHubEvent):
logging.info('Python EventHub trigger processed an event: %s',
event.get_body().decode('utf-8'))
```
触发器的核心是 `main` 函数。每当 IoT Hub 发送消息时,此函数都会被调用,并将消息作为 `event` 参数传递,同时还会传递与上一课中看到的注释相同的属性。
此函数的核心是记录事件。
* `function.json` - 此文件包含触发器的配置。主要配置在名为 `bindings` 的部分中。绑定是 Azure Functions 与其他 Azure 服务之间连接的术语。此函数有一个输入绑定到事件中心——它连接到事件中心并接收数据。
> 💁 您还可以添加输出绑定,以便函数的输出发送到另一个服务。例如,您可以添加一个输出绑定到数据库,并从函数返回 IoT Hub 事件,它将自动插入到数据库中。
✅ 做一些研究:阅读 [Azure Functions 触发器和绑定概念文档](https://docs.microsoft.com/azure/azure-functions/functions-triggers-bindings?WT.mc_id=academic-17441-jabenn&tabs=python) 中的绑定相关内容。
`bindings` 部分包括绑定的配置。以下值值得关注:
* `"type": "eventHubTrigger"` - 指定函数需要监听来自事件中心的事件
* `"name": "events"` - 这是事件中心事件的参数名称,与 Python 代码中 `main` 函数的参数名称匹配
* `"direction": "in"` - 这是一个输入绑定,事件中心的数据进入函数
* `"connection": ""` - 定义读取连接字符串的设置名称。在本地运行时,将从 `local.settings.json` 文件中读取此设置。
> 💁 连接字符串不能存储在 `function.json` 文件中,必须从设置中读取。这是为了防止意外暴露连接字符串。
1. 由于 [Azure Functions 模板中的一个错误](https://github.com/Azure/azure-functions-templates/issues/1250)`function.json` 文件中的 `cardinality` 字段值不正确。将此字段从 `many` 更新为 `one`
```json
"cardinality": "one",
```
1. 更新 `function.json` 文件中 `"connection"` 的值,使其指向您在 `local.settings.json` 文件中添加的新值:
```json
"connection": "IOT_HUB_CONNECTION_STRING",
```
> 💁 请记住 - 这里需要指向设置,而不是包含实际的连接字符串。
1. 连接字符串包含 `eventHubName` 值,因此 `function.json` 文件中的此值需要清空。将此值更新为空字符串:
```json
"eventHubName": "",
```
### 任务 - 运行事件触发器
1. 确保您没有运行 IoT Hub 事件监视器。如果事件监视器与 Functions 应用程序同时运行Functions 应用程序将无法连接并消费事件。
> 💁 多个应用程序可以使用不同的 *消费者组* 连接到 IoT Hub 的端点。这将在后续课程中介绍。
1. 要运行 Functions 应用程序,请从 VS Code 终端运行以下命令:
```sh
func start
```
Functions 应用程序将启动,并发现 `iot-hub-trigger` 函数。它将处理过去一天内发送到 IoT Hub 的任何事件。
```output
(.venv) ➜ soil-moisture-trigger func start
Found Python version 3.9.1 (python3).
Azure Functions Core Tools
Core Tools Version: 3.0.3442 Commit hash: 6bfab24b2743f8421475d996402c398d2fe4a9e0 (64-bit)
Function Runtime Version: 3.0.15417.0
Functions:
iot-hub-trigger: eventHubTrigger
For detailed output, run func with --verbose flag.
[2021-05-05T02:44:07.517Z] Worker process started and initialized.
[2021-05-05T02:44:09.202Z] Executing 'Functions.iot-hub-trigger' (Reason='(null)', Id=802803a5-eae9-4401-a1f4-176631456ce4)
[2021-05-05T02:44:09.205Z] Trigger Details: PartitionId: 0, Offset: 1011240-1011632, EnqueueTimeUtc: 2021-05-04T19:04:04.2030000Z-2021-05-04T19:04:04.3900000Z, SequenceNumber: 2546-2547, Count: 2
[2021-05-05T02:44:09.352Z] Python EventHub trigger processed an event: {"soil_moisture":628}
[2021-05-05T02:44:09.354Z] Python EventHub trigger processed an event: {"soil_moisture":624}
[2021-05-05T02:44:09.395Z] Executed 'Functions.iot-hub-trigger' (Succeeded, Id=802803a5-eae9-4401-a1f4-176631456ce4, Duration=245ms)
```
每次调用函数时,输出中都会显示 `Executing 'Functions.iot-hub-trigger'``Executed 'Functions.iot-hub-trigger'` 块,您可以看到每次函数调用处理了多少消息。
1. 确保您的 IoT 设备正在运行,您将在 Functions 应用程序中看到新的土壤湿度消息。
1. 停止并重新启动 Functions 应用程序。您会发现它不会再次处理之前的消息,只会处理新的消息。
> 💁 VS Code 还支持调试您的 Functions。您可以通过点击代码行开始处的边框设置断点或者将光标放在代码行上并选择 *Run -> Toggle breakpoint*,或者按 `F9`。您可以通过选择 *Run -> Start debugging*,按 `F5`,或选择 *Run and debug* 面板并点击 **Start debugging** 按钮启动调试器。通过这样做,您可以查看正在处理的事件的详细信息。
#### 故障排除
* 如果出现以下错误:
```output
The listener for function 'Functions.iot-hub-trigger' was unable to start. Microsoft.WindowsAzure.Storage: Connection refused. System.Net.Http: Connection refused. System.Private.CoreLib: Connection refused.
```
检查 Azurite 是否正在运行,并且您已在 `local.settings.json` 文件中将 `AzureWebJobsStorage` 设置为 `UseDevelopmentStorage=true`
* 如果出现以下错误:
```output
System.Private.CoreLib: Exception while executing function: Functions.iot-hub-trigger. System.Private.CoreLib: Result: Failure Exception: AttributeError: 'list' object has no attribute 'get_body'
```
检查您是否已将 `function.json` 文件中的 `cardinality` 设置为 `one`
* 如果出现以下错误:
```output
Azure.Messaging.EventHubs: The path to an Event Hub may be specified as part of the connection string or as a separate value, but not both. Please verify that your connection string does not have the `EntityPath` token if you are passing an explicit Event Hub name. (Parameter 'connectionString').
```
检查您是否已将 `function.json` 文件中的 `eventHubName` 设置为空字符串。
## 从无服务器代码发送直接方法请求
到目前为止,您的 Functions 应用程序正在使用事件中心兼容端点监听来自 IoT Hub 的消息。现在,您需要向 IoT 设备发送命令。这可以通过 IoT Hub 的 *注册管理器* 连接来完成。注册管理器是一个工具,允许您查看注册到 IoT Hub 的设备,并通过发送云到设备消息、直接方法请求或更新设备双向通信与这些设备进行交互。您还可以使用它注册、更新或删除 IoT Hub 中的 IoT 设备。
要连接到注册管理器,您需要一个连接字符串。
### 任务 - 获取注册管理器连接字符串
1. 要获取连接字符串,请运行以下命令:
```sh
az iot hub connection-string show --policy-name service \
--output table \
--hub-name <hub_name>
```
`<hub_name>` 替换为您为 IoT Hub 使用的名称。
连接字符串是为 *ServiceConnect* 策略请求的,使用 `--policy-name service` 参数。当您请求连接字符串时可以指定该连接字符串允许的权限。ServiceConnect 策略允许您的代码连接并向 IoT 设备发送消息。
✅ 做一些研究:阅读 [IoT Hub 权限文档](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-security#iot-hub-permissions?WT.mc_id=academic-17441-jabenn) 中的不同策略。
1. 在 VS Code 中打开 `local.settings.json` 文件。在 `Values` 部分中添加以下值:
```json
"REGISTRY_MANAGER_CONNECTION_STRING": "<connection string>"
```
`<connection string>` 替换为上一步中的值。您需要在上一行后添加一个逗号以使其成为有效的 JSON。
### 任务 - 向设备发送直接方法请求
1. 注册管理器的 SDK 可通过 Pip 包获得。向 `requirements.txt` 文件添加以下行以添加对该包的依赖:
```sh
azure-iot-hub
```
1. 确保 VS Code 终端已激活虚拟环境,并运行以下命令安装 Pip 包:
```sh
pip install -r requirements.txt
```
1. 向 `__init__.py` 文件添加以下导入:
```python
import json
import os
from azure.iot.hub import IoTHubRegistryManager
from azure.iot.hub.models import CloudToDeviceMethod
```
这导入了一些系统库,以及与注册管理器交互并发送直接方法请求的库。
1. 删除 `main` 方法中的代码,但保留方法本身。
1. 在 `main` 方法中添加以下代码:
```python
body = json.loads(event.get_body().decode('utf-8'))
device_id = event.iothub_metadata['connection-device-id']
logging.info(f'Received message: {body} from {device_id}')
```
此代码提取事件的主体,其中包含 IoT 设备发送的 JSON 消息。
然后从消息的注释中获取设备 ID。事件的主体包含作为遥测发送的消息`iothub_metadata` 字典包含 IoT Hub 设置的属性,例如发送者的设备 ID 和消息发送的时间。
此信息随后被记录。您将在本地运行 Functions 应用程序时在终端中看到这些日志。
1. 在此代码下方添加以下代码:
```python
soil_moisture = body['soil_moisture']
if soil_moisture > 450:
direct_method = CloudToDeviceMethod(method_name='relay_on', payload='{}')
else:
direct_method = CloudToDeviceMethod(method_name='relay_off', payload='{}')
```
此代码从消息中获取土壤湿度值。然后检查土壤湿度值,并根据值创建一个辅助类,用于 `relay_on``relay_off` 直接方法请求。方法请求不需要负载,因此发送一个空的 JSON 文档。
1. 在此代码下方添加以下代码:
```python
logging.info(f'Sending direct method request for {direct_method.method_name} for device {device_id}')
registry_manager_connection_string = os.environ['REGISTRY_MANAGER_CONNECTION_STRING']
registry_manager = IoTHubRegistryManager(registry_manager_connection_string)
```
此代码从 `local.settings.json` 文件中加载 `REGISTRY_MANAGER_CONNECTION_STRING`。该文件中的值会作为环境变量提供,可以通过 `os.environ` 函数读取,该函数返回一个包含所有环境变量的字典。
> 💁 当此代码部署到云端时,`local.settings.json` 文件中的值将被设置为 *应用程序设置*,并可以通过环境变量读取。
然后代码使用连接字符串创建了一个 Registry Manager 辅助类的实例。
1. 在下面添加以下代码:
```python
registry_manager.invoke_device_method(device_id, direct_method)
logging.info('Direct method request sent!')
```
此代码指示注册管理器向发送遥测数据的设备发送直接方法请求。
> 💁 在之前课程中使用 MQTT 创建的应用版本中,继电器控制命令会发送到所有设备。代码假设您只有一个设备。而此版本的代码将方法请求发送到单个设备,因此如果您有多个湿度传感器和继电器的设置,它可以向正确的设备发送正确的直接方法请求。
1. 运行 Functions 应用程序,并确保您的 IoT 设备正在发送数据。您将看到消息被处理并发送直接方法请求。将土壤湿度传感器插入或移出土壤,观察值的变化以及继电器的开关状态。
> 💁 您可以在 [code/functions](../../../../../2-farm/lessons/5-migrate-application-to-the-cloud/code/functions) 文件夹中找到此代码。
## 将您的无服务器代码部署到云端
您的代码现在已经在本地运行,下一步是将 Functions 应用程序部署到云端。
### 任务 - 创建云资源
您的 Functions 应用程序需要部署到 Azure 中的 Functions 应用资源,该资源位于您为 IoT Hub 创建的资源组中。您还需要在 Azure 中创建一个存储账户,以替代您在本地运行的模拟存储。
1. 运行以下命令以创建存储账户:
```sh
az storage account create --resource-group soil-moisture-sensor \
--sku Standard_LRS \
--name <storage_name>
```
`<storage_name>` 替换为您的存储账户名称。此名称需要在全球范围内唯一,因为它是访问存储账户的 URL 的一部分。此名称只能使用小写字母和数字,不能包含其他字符,并且限制为 24 个字符。可以使用类似 `sms` 的名称,并在末尾添加唯一标识符,例如一些随机单词或您的名字。
`--sku Standard_LRS` 选项选择定价层,选择最低成本的通用账户。存储没有免费层,您需要为使用的部分付费。成本相对较低,最贵的存储每月每存储 1GB 的费用不到 0.05 美元。
✅ 在 [Azure 存储账户定价页面](https://azure.microsoft.com/pricing/details/storage/?WT.mc_id=academic-17441-jabenn) 上了解定价信息。
1. 运行以下命令以创建 Functions 应用程序:
```sh
az functionapp create --resource-group soil-moisture-sensor \
--runtime python \
--functions-version 3 \
--os-type Linux \
--consumption-plan-location <location> \
--storage-account <storage_name> \
--name <functions_app_name>
```
`<location>` 替换为您在上一课中创建资源组时使用的位置。
`<storage_name>` 替换为您在上一步中创建的存储账户名称。
`<functions_app_name>` 替换为您的 Functions 应用程序的唯一名称。此名称需要在全球范围内唯一,因为它是访问 Functions 应用程序的 URL 的一部分。可以使用类似 `soil-moisture-sensor-` 的名称,并在末尾添加唯一标识符,例如一些随机单词或您的名字。
`--functions-version 3` 选项设置 Azure Functions 的版本。版本 3 是最新版本。
`--os-type Linux` 告诉 Functions 运行时使用 Linux 作为托管这些函数的操作系统。Functions 可以托管在 Linux 或 Windows 上具体取决于使用的编程语言。Python 应用程序仅支持 Linux。
### 任务 - 上传您的应用程序设置
在开发 Functions 应用程序时,您将一些设置存储在 `local.settings.json` 文件中,用于 IoT Hub 的连接字符串。这些设置需要写入 Azure 中的 Functions 应用程序的应用程序设置,以便您的代码可以使用它们。
> 🎓 `local.settings.json` 文件仅用于本地开发设置,不应提交到源代码控制系统(如 GitHub。当部署到云端时会使用应用程序设置。应用程序设置是托管在云端的键值对可以通过代码或运行时从环境变量中读取用于将代码连接到 IoT Hub。
1. 运行以下命令以在 Functions 应用程序的应用程序设置中设置 `IOT_HUB_CONNECTION_STRING`
```sh
az functionapp config appsettings set --resource-group soil-moisture-sensor \
--name <functions_app_name> \
--settings "IOT_HUB_CONNECTION_STRING=<connection string>"
```
`<functions_app_name>` 替换为您为 Functions 应用程序使用的名称。
`<connection string>` 替换为 `local.settings.json` 文件中的 `IOT_HUB_CONNECTION_STRING` 的值。
1. 重复上述步骤,但将 `REGISTRY_MANAGER_CONNECTION_STRING` 的值设置为 `local.settings.json` 文件中的对应值。
运行这些命令后,它们还会输出 Functions 应用程序的所有应用程序设置列表。您可以使用此列表检查值是否设置正确。
> 💁 您会看到一个已经设置的 `AzureWebJobsStorage` 值。在您的 `local.settings.json` 文件中,此值被设置为使用本地存储模拟器。当您创建 Functions 应用程序时,传递存储账户作为参数,此设置会自动设置。
### 任务 - 将您的 Functions 应用程序部署到云端
现在 Functions 应用程序已经准备好,可以部署您的代码。
1. 从 VS Code 终端运行以下命令以发布您的 Functions 应用程序:
```sh
func azure functionapp publish <functions_app_name>
```
`<functions_app_name>` 替换为您为 Functions 应用程序使用的名称。
代码将被打包并发送到 Functions 应用程序,在那里它将被部署并启动。会有大量的控制台输出,最后确认部署完成并列出已部署的函数。在此情况下,列表中只会包含触发器。
```output
Deployment successful.
Remote build succeeded!
Syncing triggers...
Functions in soil-moisture-sensor:
iot-hub-trigger - [eventHubTrigger]
```
确保您的 IoT 设备正在运行。通过调整土壤湿度或将传感器插入或移出土壤来改变湿度水平。您将看到继电器随着土壤湿度的变化而打开和关闭。
---
## 🚀 挑战
在上一课中,您通过在继电器打开时以及关闭后的一段时间内取消订阅 MQTT 消息来管理继电器的计时。在这里您无法使用此方法——您无法取消订阅 IoT Hub 触发器。
思考在 Functions 应用程序中可以使用哪些不同的方法来处理这一问题。
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/18)
## 复习与自学
* 在 [维基百科的无服务器计算页面](https://wikipedia.org/wiki/Serverless_computing) 上阅读有关无服务器计算的内容
* 阅读关于在 Azure 中使用无服务器的内容,包括更多示例:[Azure 博客文章 - 为您的 IoT 需求选择无服务器](https://azure.microsoft.com/blog/go-serverless-for-your-iot-needs/?WT.mc_id=academic-17441-jabenn)
* 在 [Azure Functions YouTube 频道](https://www.youtube.com/c/AzureFunctions) 上了解更多关于 Azure Functions 的内容
## 作业
[添加手动继电器控制](assignment.md)
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,59 @@
# 添加手动继电器控制
## 说明
无服务器代码可以由多种方式触发,包括 HTTP 请求。你可以使用 HTTP 触发器为继电器控制添加手动覆盖功能,从而允许通过网页请求来打开或关闭继电器。
在本任务中,你需要为你的 Functions App 添加两个 HTTP 触发器,用于打开和关闭继电器,复用本课中学到的知识向设备发送命令。
一些提示:
* 你可以使用以下命令为现有的 Functions App 添加一个 HTTP 触发器:
```sh
func new --name <trigger name> --template "HTTP trigger"
```
`<trigger name>` 替换为你的 HTTP 触发器的名称。可以使用类似 `relay_on``relay_off` 的名称。
* HTTP 触发器可以设置访问控制。默认情况下,它们需要一个特定于函数的 API 密钥随 URL 一起传递才能运行。对于本任务,你可以移除此限制,以便任何人都可以运行该函数。为此,请在 HTTP 触发器的 `function.json` 文件中将 `authLevel` 设置更新为以下内容:
```json
"authLevel": "anonymous"
```
> 💁 你可以在 [Function access keys documentation](https://docs.microsoft.com/azure/azure-functions/functions-bindings-http-webhook-trigger?WT.mc_id=academic-17441-jabenn#authorization-keys) 中阅读更多关于此访问控制的信息。
* HTTP 触发器默认支持 GET 和 POST 请求。这意味着你可以使用网页浏览器调用它们——网页浏览器会发出 GET 请求。
当你在本地运行 Functions App 时,你会看到触发器的 URL
```output
Functions:
relay_off: [GET,POST] http://localhost:7071/api/relay_off
relay_on: [GET,POST] http://localhost:7071/api/relay_on
iot-hub-trigger: eventHubTrigger
```
将 URL 粘贴到浏览器中并按下 `回车`,或者在 VS Code 的终端窗口中 `Ctrl+点击`macOS 上为 `Cmd+点击`)链接以在默认浏览器中打开。这将运行触发器。
> 💁 注意 URL 中包含 `/api`——HTTP 触发器默认位于 `api` 子域中。
* 当你部署 Functions App 时HTTP 触发器的 URL 将是:
`https://<functions app name>.azurewebsites.net/api/<trigger name>`
其中 `<functions app name>` 是你的 Functions App 的名称,`<trigger name>` 是触发器的名称。
## 评分标准
| 标准 | 优秀 | 合格 | 需要改进 |
| ---- | ---- | ---- | -------- |
| 创建 HTTP 触发器 | 创建了两个用于打开和关闭继电器的触发器,并使用了合适的名称 | 创建了一个具有合适名称的触发器 | 未能创建任何触发器 |
| 从 HTTP 触发器控制继电器 | 能够将两个触发器连接到 IoT Hub 并正确控制继电器 | 能够将一个触发器连接到 IoT Hub 并正确控制继电器 | 未能将触发器连接到 IoT Hub |
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,234 @@
# 保持植物安全
![本课的概述手绘图](../../../../../translated_images/zh-CN/lesson-10.829c86b80b9403bb770929ee553a1d293afe50dc23121aaf9be144673ae012cc.jpg)
> 手绘图由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看更大版本。
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/19)
## 简介
在之前的课程中,你已经创建了一个土壤监测物联网设备并将其连接到云端。但如果竞争农场的黑客控制了你的物联网设备会怎样?如果他们发送虚假的高土壤湿度数据,让你的植物永远得不到浇水,或者让你的灌溉系统一直运行,导致植物因过度浇水而死亡,同时让你在水费上损失惨重呢?
在本课中,你将学习如何保护物联网设备的安全。由于这是本项目的最后一课,你还将学习如何清理云资源,以减少潜在的成本。
本课将涵盖以下内容:
* [为什么需要保护物联网设备的安全?](../../../../../2-farm/lessons/6-keep-your-plant-secure)
* [加密技术](../../../../../2-farm/lessons/6-keep-your-plant-secure)
* [保护你的物联网设备](../../../../../2-farm/lessons/6-keep-your-plant-secure)
* [生成并使用 X.509 证书](../../../../../2-farm/lessons/6-keep-your-plant-secure)
> 🗑 这是本项目的最后一课,因此在完成本课和作业后,不要忘记清理你的云服务。你需要这些服务来完成作业,所以请确保先完成作业。
>
> 如果需要,请参考[项目清理指南](../../../clean-up.md)以获取相关操作说明。
## 为什么需要保护物联网设备的安全?
物联网安全涉及确保只有预期的设备可以连接到你的云物联网服务并发送遥测数据,同时只有你的云服务可以向设备发送命令。物联网数据可能包含个人信息,包括医疗或隐私数据,因此整个应用程序需要考虑安全性,以防止数据泄露。
如果你的物联网应用程序不安全,会面临以下风险:
* 假设备可能发送错误数据,导致你的应用程序做出错误响应。例如,它们可能发送持续的高土壤湿度数据,导致你的灌溉系统永远不启动,植物因缺水而死亡。
* 未授权用户可能读取物联网设备的数据,包括个人或业务关键数据。
* 黑客可能发送命令以控制设备,从而对设备或连接的硬件造成损害。
* 通过连接到物联网设备,黑客可能利用此设备访问其他网络,从而进入私人系统。
* 恶意用户可能访问个人数据并利用这些数据进行勒索。
这些都是现实中的场景,并且经常发生。之前的课程中已经提到了一些例子,以下是更多案例:
* 2018年黑客利用鱼缸温控器的开放WiFi接入点进入一家赌场的网络并窃取数据。[The Hacker News - Casino Gets Hacked Through Its Internet-Connected Fish Tank Thermometer](https://thehackernews.com/2018/04/iot-hacking-thermometer.html)
* 2016年Mirai僵尸网络对互联网服务提供商Dyn发起了拒绝服务攻击导致大部分互联网瘫痪。该僵尸网络利用恶意软件连接到使用默认用户名和密码的物联网设备如DVR和摄像头并从那里发起攻击。[The Guardian - DDoS attack that disrupted internet was largest of its kind in history, experts say](https://www.theguardian.com/technology/2016/oct/26/ddos-attack-dyn-mirai-botnet)
* Spiral Toys的CloudPets连接玩具用户数据库在互联网上公开可访问。[Troy Hunt - Data from connected CloudPets teddy bears leaked and ransomed, exposing kids' voice messages](https://www.troyhunt.com/data-from-connected-cloudpets-teddy-bears-leaked-and-ransomed-exposing-kids-voice-messages/)
* Strava标记了你跑步时经过的其他跑者并显示他们的路线使陌生人能够看到你住在哪里。[Kim Komndo - Fitness app could lead a stranger right to your home — change this setting](https://www.komando.com/security-privacy/strava-fitness-app-privacy/755349/)
✅ 做一些研究:搜索更多物联网数据泄露或黑客攻击的例子,尤其是与个人物品相关的案例,例如联网牙刷或体重秤。思考这些攻击可能对受害者或客户造成的影响。
> 💁 安全是一个非常广泛的话题,本课只会涉及设备连接到云端的一些基础知识。未涵盖的主题包括监控数据在传输中的变化、直接攻击设备或更改设备配置。物联网黑客威胁如此之大,以至于像 [Azure Defender for IoT](https://azure.microsoft.com/services/azure-defender-for-iot/?WT.mc_id=academic-17441-jabenn) 这样的工具已经被开发出来。这些工具类似于你电脑上的杀毒和安全工具,但专为小型、低功耗的物联网设备设计。
## 加密技术
当设备连接到物联网服务时它会使用一个ID来标识自己。问题是这个ID可能会被克隆——黑客可以设置一个恶意设备使用与真实设备相同的ID但发送虚假数据。
![有效设备和恶意设备可能使用相同的ID发送遥测数据](../../../../../translated_images/zh-CN/iot-device-and-hacked-device-connecting.e0671675df74d6d99eb1dedb5a670e606f698efa6202b1ad4c8ae548db299cc6.png)
解决方法是将发送的数据转换为一种加密格式,使用设备和云端都知道的某种值来加密数据。这一过程称为*加密*,用于加密数据的值称为*加密密钥*。
![如果使用加密,则只有加密消息会被接受,其他消息会被拒绝](../../../../../translated_images/zh-CN/iot-device-and-hacked-device-connecting-encryption.5941aff601fc978f979e46f2849b573564eeb4a4dc5b52f669f62745397492fb.png)
云服务可以使用一个称为*解密*的过程将数据转换回可读格式,使用相同的加密密钥或一个*解密密钥*。如果加密消息无法通过密钥解密,则说明设备已被黑客攻击,消息会被拒绝。
执行加密和解密的技术称为*密码学*。
### 早期密码学
最早的密码学类型是替换密码最早可追溯到3500年前。替换密码涉及用一个字母替换另一个字母。例如[凯撒密码](https://wikipedia.org/wiki/Caesar_cipher)通过定义的数量来移动字母表,只有加密消息的发送者和接收者知道移动了多少个字母。
[维吉尼亚密码](https://wikipedia.org/wiki/Vigenère_cipher)进一步发展,通过使用单词来加密文本,使原始文本中的每个字母移动的数量不同,而不是始终移动相同数量的字母。
密码学被广泛用于各种目的,例如在古代美索不达米亚保护陶工釉料配方、在印度写秘密情书或在古埃及保护魔法咒语。
### 现代密码学
现代密码学更加先进,比早期方法更难破解。现代密码学使用复杂的数学方法加密数据,可能的密钥数量多到无法通过暴力破解。
密码学在安全通信中有很多用途。如果你正在GitHub上阅读此页面你可能会注意到网站地址以*HTTPS*开头这意味着你的浏览器与GitHub的网络服务器之间的通信是加密的。如果有人能够读取你浏览器与GitHub之间的互联网流量他们也无法读取数据因为数据是加密的。你的电脑甚至可能加密硬盘上的所有数据这样如果有人偷走了你的电脑他们无法在没有密码的情况下读取任何数据。
> 🎓 HTTPS代表超文本传输协议**安全**
不幸的是并非所有设备都是安全的。有些设备没有任何安全措施有些设备使用容易破解的密钥有些设备甚至所有同类型设备都使用相同的密钥。有些非常个人化的物联网设备被发现所有设备的WiFi或蓝牙连接密码都是一样的。如果你能连接到自己的设备你也能连接到别人的设备。一旦连接你可能访问非常隐私的数据甚至控制他们的设备。
> 💁 尽管现代密码学复杂且声称破解加密可能需要数十亿年,但量子计算的兴起使得在极短时间内破解所有已知加密成为可能!
### 对称密钥和非对称密钥
加密分为两种类型——对称加密和非对称加密。
**对称加密**使用相同的密钥来加密和解密数据。发送者和接收者都需要知道相同的密钥。这种方式安全性较低,因为密钥需要以某种方式共享。发送者在发送加密消息给接收者之前,可能需要先将密钥发送给接收者。
![对称密钥加密使用相同的密钥加密和解密消息](../../../../../translated_images/zh-CN/send-message-symmetric-key.a2e8ad0d495896ff.webp)
如果密钥在传输过程中被盗,或者发送者或接收者被黑客攻击并泄露了密钥,加密就会被破解。
![对称密钥加密只有在黑客未获取密钥时才安全——如果密钥被盗,黑客可以拦截并解密消息](../../../../../translated_images/zh-CN/send-message-symmetric-key-hacker.e7cb53db1707adfb.webp)
**非对称加密**使用两个密钥——一个加密密钥和一个解密密钥,称为公钥/私钥对。公钥用于加密消息,但不能用于解密;私钥用于解密消息,但不能用于加密。
![非对称加密使用不同的密钥加密和解密。加密密钥会发送给消息发送者,以便他们在发送消息给拥有密钥的接收者之前加密消息](../../../../../translated_images/zh-CN/send-message-asymmetric.7abe327c62615b8c.webp)
接收者共享他们的公钥,发送者使用公钥加密消息。一旦消息发送,接收者使用私钥解密消息。非对称加密更安全,因为私钥由接收者保密,从不共享。任何人都可以拥有公钥,因为它只能用于加密消息。
对称加密比非对称加密速度更快,而非对称加密更安全。一些系统会同时使用两者——使用非对称加密来加密和共享对称密钥,然后使用对称密钥加密所有数据。这种方式使得共享对称密钥更安全,同时在加密和解密数据时更快。
## 保护你的物联网设备
物联网设备可以使用对称或非对称加密进行保护。对称加密更简单,但安全性较低。
### 对称密钥
当你设置物联网设备与IoT Hub交互时你使用了一个连接字符串。一个示例连接字符串如下
```output
HostName=soil-moisture-sensor.azure-devices.net;DeviceId=soil-moisture-sensor;SharedAccessKey=Bhry+ind7kKEIDxubK61RiEHHRTrPl7HUow8cEm/mU0=
```
这个连接字符串由三个部分组成,用分号分隔,每部分包含一个键和值:
| 键 | 值 | 描述 |
| --- | ----- | ----------- |
| HostName | `soil-moisture-sensor.azure-devices.net` | IoT Hub的URL |
| DeviceId | `soil-moisture-sensor` | 设备的唯一ID |
| SharedAccessKey | `Bhry+ind7kKEIDxubK61RiEHHRTrPl7HUow8cEm/mU0=` | 设备和IoT Hub都知道的对称密钥 |
连接字符串的最后一部分`SharedAccessKey`是设备和IoT Hub都知道的对称密钥。这个密钥不会从设备发送到云端也不会从云端发送到设备而是用于加密发送或接收的数据。
✅ 做一个实验:如果你在连接物联网设备时更改连接字符串中的`SharedAccessKey`部分,会发生什么?试试看。
当设备首次尝试连接时它会发送一个共享访问签名SAS令牌该令牌由IoT Hub的URL、访问签名的过期时间戳通常是当前时间起1天和一个签名组成。这个签名由URL和过期时间通过连接字符串中的共享访问密钥加密而成。
IoT Hub使用共享访问密钥解密这个签名如果解密后的值与URL和过期时间匹配设备就被允许连接。它还会验证当前时间是否在过期时间之前以防止恶意设备捕获真实设备的SAS令牌并使用它。
这是验证发送者是否为正确设备的一种优雅方式。通过同时发送一些已知数据的未加密和加密版本,服务器可以通过解密加密数据并确保结果与发送的未加密版本匹配来验证设备。如果匹配,则说明发送者和接收者拥有相同的对称加密密钥。
💁 由于到期时间的限制,您的 IoT 设备需要知道准确的时间,通常从 [NTP](https://wikipedia.org/wiki/Network_Time_Protocol) 服务器读取。如果时间不准确,连接将会失败。
连接后,从设备发送到 IoT Hub 或从 IoT Hub 发送到设备的所有数据都将使用共享访问密钥进行加密。
✅ 你认为如果多个设备共享相同的连接字符串会发生什么?
> 💁 将密钥存储在代码中是一种不好的安全实践。如果黑客获取了你的源代码,他们就能获取你的密钥。此外,在发布代码时也会更加困难,因为你需要为每个设备使用更新的密钥重新编译代码。更好的做法是从硬件安全模块加载密钥——这是一个存储加密值的 IoT 设备芯片,代码可以读取这些值。
>
> 在学习 IoT 时,将密钥放入代码中通常更容易,就像你在之前的课程中所做的那样,但你必须确保这个密钥不会被提交到公共的源代码控制中。
设备有两个密钥和两个对应的连接字符串。这使得你可以轮换密钥——也就是说,如果第一个密钥被泄露,可以切换到另一个密钥,并重新生成第一个密钥。
### X.509 证书
当你使用公钥/私钥对进行非对称加密时,你需要将公钥提供给任何想要向你发送数据的人。问题是,接收者如何确定这确实是你的公钥,而不是某人冒充你的?与其直接提供密钥,你可以将公钥包含在一个由可信第三方验证的证书中,这种证书被称为 X.509 证书。
X.509 证书是包含公钥部分的数字文档。它们通常由被称为[认证机构](https://wikipedia.org/wiki/Certificate_authority) (CAs) 的可信组织之一颁发,并由 CA 数字签名以表明密钥是有效的并且确实来自你。你信任证书以及证书中公钥的来源,因为你信任 CA这类似于你信任护照或驾驶执照因为你信任颁发它的国家。证书是需要付费的但你也可以“自签名”即自己创建一个由你签名的证书用于测试目的。
> 💁 你绝不应该在生产环境中使用自签名证书。
这些证书包含许多字段,包括公钥的来源、颁发证书的 CA 的详细信息、证书的有效期以及公钥本身。在使用证书之前,最好通过检查它是否由原始 CA 签名来验证它。
✅ 你可以在 [Microsoft 关于 X.509 公钥证书的教程](https://docs.microsoft.com/azure/iot-hub/tutorial-x509-certificates?WT.mc_id=academic-17441-jabenn#certificate-fields) 中阅读证书字段的完整列表。
使用 X.509 证书时,发送方和接收方都会拥有自己的公钥和私钥,并且双方都会有包含公钥的 X.509 证书。然后,他们以某种方式交换 X.509 证书,使用对方的公钥加密发送的数据,并使用自己的私钥解密接收到的数据。
![与其共享公钥,不如共享证书。证书的使用者可以通过检查签署证书的认证机构来验证它确实来自你。](../../../../../translated_images/zh-CN/send-message-certificate.9cc576ac1e46b76e.webp)
使用 X.509 证书的一个主要优势是它们可以在设备之间共享。你可以创建一个证书,将其上传到 IoT Hub并将其用于所有设备。每个设备只需要知道私钥即可解密从 IoT Hub 接收到的消息。
设备用于加密发送到 IoT Hub 的消息的证书是由 Microsoft 发布的。这是许多 Azure 服务使用的相同证书,有时已经内置在 SDK 中。
> 💁 请记住公钥就是“公开”的。Azure 公钥只能用于加密发送到 Azure 的数据,而不能用于解密,因此可以随处共享,包括在源代码中。例如,你可以在 [Azure IoT C SDK 源代码](https://github.com/Azure/azure-iot-sdk-c/blob/master/certs/certs.c) 中看到它。
✅ X.509 证书中有许多术语。你可以在 [X.509 证书术语的通俗指南](https://techcommunity.microsoft.com/t5/internet-of-things/the-layman-s-guide-to-x-509-certificate-jargon/ba-p/2203540?WT.mc_id=academic-17441-jabenn) 中阅读一些你可能遇到的术语的定义。
## 生成并使用 X.509 证书
生成 X.509 证书的步骤如下:
1. 创建一个公钥/私钥对。最广泛使用的生成公钥/私钥对的算法之一是 [RivestShamirAdleman](https://wikipedia.org/wiki/RSA_(cryptosystem)) (RSA)。
1. 提交公钥及相关数据以供签名,可以由 CA 签名,也可以自签名。
Azure CLI 提供了在 IoT Hub 中创建新设备标识并自动生成公钥/私钥对以及创建自签名证书的命令。
> 💁 如果你想了解详细步骤,而不是使用 Azure CLI你可以在 [Microsoft IoT Hub 文档中关于使用 OpenSSL 创建自签名证书的教程](https://docs.microsoft.com/azure/iot-hub/tutorial-x509-self-sign?WT.mc_id=academic-17441-jabenn) 中找到。
### 任务 - 使用 X.509 证书创建设备标识
1. 运行以下命令注册新的设备标识,并自动生成密钥和证书:
```sh
az iot hub device-identity create --device-id soil-moisture-sensor-x509 \
--am x509_thumbprint \
--output-dir . \
--hub-name <hub_name>
```
`<hub_name>` 替换为你为 IoT Hub 使用的名称。
这将创建一个 ID 为 `soil-moisture-sensor-x509` 的设备,以区别于你在上一课中创建的设备标识。此命令还将在当前目录中创建两个文件:
* `soil-moisture-sensor-x509-key.pem` - 此文件包含设备的私钥。
* `soil-moisture-sensor-x509-cert.pem` - 这是设备的 X.509 证书文件。
请妥善保管这些文件!私钥文件不应被提交到公共的源代码控制中。
### 任务 - 在设备代码中使用 X.509 证书
按照相关指南,通过 X.509 证书将你的 IoT 设备连接到云端:
* [Arduino - Wio Terminal](wio-terminal-x509.md)
* [单板计算机 - Raspberry Pi/虚拟 IoT 设备](single-board-computer-x509.md)
---
## 🚀 挑战
创建、管理和删除 Azure 服务(如资源组和 IoT Hub有多种方法。其中一种方法是 [Azure Portal](https://portal.azure.com?WT.mc_id=academic-17441-jabenn)——一个基于 Web 的界面,提供 GUI 来管理你的 Azure 服务。
访问 [portal.azure.com](https://portal.azure.com?WT.mc_id=academic-17441-jabenn) 并探索门户。看看你是否可以使用门户创建一个 IoT Hub然后将其删除。
**提示** - 通过门户创建服务时,你不需要提前创建资源组,可以在创建服务时创建资源组。完成后,请确保将其删除!
你可以在 [Azure Portal 文档](https://docs.microsoft.com/azure/azure-portal/?WT.mc_id=academic-17441-jabenn) 中找到大量文档、教程和指南。
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/20)
## 复习与自学
* 阅读 [Wikipedia 上的密码学历史页面](https://wikipedia.org/wiki/History_of_cryptography) 了解密码学的历史。
* 阅读 [Wikipedia 上的 X.509 页面](https://wikipedia.org/wiki/X.509) 了解 X.509 证书。
## 作业
[构建一个新的 IoT 设备](assignment.md)
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们不对因使用此翻译而产生的任何误解或误读承担责任。

@ -0,0 +1,18 @@
# 构建一个新的物联网设备
## 说明
在过去的6节课中你已经学习了数字农业的相关知识以及如何使用物联网设备收集数据来预测植物生长并根据土壤湿度读数自动浇水。
利用你所学的知识,使用一个传感器和一个执行器来构建一个新的物联网设备。将遥测数据发送到物联网中心,并通过无服务器代码来控制执行器。你可以使用本项目或之前项目中已经使用过的传感器和执行器,也可以尝试使用其他硬件来尝试新的方法。
## 评分标准
| 标准 | 卓越 | 合格 | 需要改进 |
| ---- | ---- | ---- | -------- |
| 编写一个物联网设备以使用传感器和执行器 | 编写了一个能够与传感器和执行器配合工作的物联网设备 | 编写了一个能够与传感器或执行器配合工作的物联网设备 | 未能编写一个能够使用传感器或执行器的物联网设备 |
| 将物联网设备连接到物联网中心 | 成功部署了物联网中心并发送遥测数据,同时接收命令 | 成功部署了物联网中心并能够发送遥测数据或接收命令 | 未能部署物联网中心或与物联网设备进行通信 |
| 使用无服务器代码控制执行器 | 成功部署了一个Azure Function通过遥测事件触发来控制设备 | 成功部署了一个通过遥测事件触发的Azure Function但未能控制执行器 | 未能部署Azure Function |
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,60 @@
# 在设备代码中使用 X.509 证书 - 虚拟 IoT 硬件和 Raspberry Pi
在本课程的这一部分中,您将使用 X.509 证书将虚拟 IoT 设备或 Raspberry Pi 连接到您的 IoT Hub。
## 将设备连接到 IoT Hub
下一步是使用 X.509 证书将设备连接到 IoT Hub。
### 任务 - 连接到 IoT Hub
1. 将密钥和证书文件复制到包含 IoT 设备代码的文件夹中。如果您通过 VS Code Remote SSH 使用 Raspberry Pi并在您的 PC 或 Mac 上创建了密钥,可以将文件拖放到 VS Code 的资源管理器中以完成复制。
1. 打开 `app.py` 文件
1. 要使用 X.509 证书进行连接,您需要 IoT Hub 的主机名和 X.509 证书。首先,通过在创建设备客户端之前添加以下代码来创建一个包含主机名的变量:
```python
host_name = "<host_name>"
```
`<host_name>` 替换为您的 IoT Hub 的主机名。您可以从 `connection_string``HostName` 部分获取主机名。它将是您的 IoT Hub 的名称,以 `.azure-devices.net` 结尾。
1. 在此代码下方,声明一个包含设备 ID 的变量:
```python
device_id = "soil-moisture-sensor-x509"
```
1. 您需要一个包含 X.509 文件的 `X509` 类实例。从 `azure.iot.device` 模块导入的类列表中添加 `X509`
```python
from azure.iot.device import IoTHubDeviceClient, Message, MethodResponse, X509
```
1. 使用您的证书和密钥文件创建一个 `X509` 类实例,在 `host_name` 声明下方添加以下代码:
```python
x509 = X509("./soil-moisture-sensor-x509-cert.pem", "./soil-moisture-sensor-x509-key.pem")
```
这将使用之前创建的 `soil-moisture-sensor-x509-cert.pem``soil-moisture-sensor-x509-key.pem` 文件创建 `X509` 类。
1. 将创建 `device_client` 的代码行替换为以下内容:
```python
device_client = IoTHubDeviceClient.create_from_x509_certificate(x509, host_name, device_id)
```
这将使用 X.509 证书而不是连接字符串进行连接。
1. 删除包含 `connection_string` 变量的代码行。
1. 运行您的代码。监控发送到 IoT Hub 的消息,并像之前一样发送直接方法请求。您将看到设备连接并发送土壤湿度读数,同时接收直接方法请求。
> 💁 您可以在 [code/pi](../../../../../2-farm/lessons/6-keep-your-plant-secure/code/pi) 或 [code/virtual-device](../../../../../2-farm/lessons/6-keep-your-plant-secure/code/virtual-device) 文件夹中找到此代码。
😀 您的土壤湿度传感器程序已使用 X.509 证书成功连接到您的 IoT Hub
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而产生的任何误解或误读不承担责任。

@ -0,0 +1,6 @@
# 在设备代码中使用 X.509 证书 - Wio Terminal
在撰写本文时Azure Arduino SDK 尚不支持 X.509 证书。如果您想尝试使用 X.509 证书,可以参考[使用 Python SDK 的虚拟 IoT 设备指南](single-board-computer-x509.md)
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。对于因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,27 @@
# 从农场到工厂的运输 - 使用物联网追踪食品运输
许多农民种植农作物用于销售——他们可能是商业农民,出售所有种植的作物,或者是自给自足的农民,出售多余的农产品以购买生活必需品。不管怎样,食品必须从农场运送到消费者手中,这通常依赖于从农场到集散中心或加工厂,再到商店的批量运输。例如,一个种植番茄的农民会收获番茄,将它们装箱,把箱子装上卡车,然后运送到加工厂。番茄会在加工厂被分类,然后以加工食品、零售销售或餐馆消费的形式送到消费者手中。
物联网可以通过追踪运输中的食品来帮助优化这一供应链——确保司机按照指定路线行驶,监控车辆位置,并在车辆到达时发出警报,以便尽快卸货并准备加工。
> 🎓 *供应链* 是指制造和交付某物的一系列活动。例如,在番茄种植中,它包括种子、土壤、肥料和水的供应,种植番茄,将番茄运送到集散中心,再运输到超市的本地中心,接着运送到各个超市,摆放在货架上,最后被消费者购买并带回家食用。每一步就像链条中的一个环节。
> 🎓 供应链中的运输部分被称为 *物流*
在这四节课中你将学习如何应用物联网来改进供应链通过监控食品被装载到虚拟卡车上并追踪其到达目的地的过程。你将学习GPS追踪、如何存储和可视化GPS数据以及如何在卡车到达目的地时收到警报。
> 💁 这些课程将使用一些云资源。如果你没有完成本项目中的所有课程,请确保[清理你的项目](../clean-up.md)。
## 主题
1. [位置追踪](lessons/1-location-tracking/README.md)
1. [存储位置信息](lessons/2-store-location-data/README.md)
1. [可视化位置信息](lessons/3-visualize-location-data/README.md)
1. [地理围栏](lessons/4-geofences/README.md)
## 致谢
所有课程由 [Jen Looper](https://github.com/jlooper) 和 [Jim Bennett](https://GitHub.com/JimBobBennett) 倾情编写 ♥️
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,205 @@
# 位置追踪
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-11.9fddbac4b664c6d50ab7ac9bb32f1fc3f945f03760e72f7f43938073762fb017.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看大图。
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/21)
## 简介
将农产品从农场运送到消费者的主要过程包括将装满农产品的箱子装载到卡车、船舶、飞机或其他商业运输工具上,并将食品运送到某个地方——要么直接送到客户手中,要么送到中央枢纽或仓库进行处理。从农场到消费者的整个端到端过程被称为*供应链*。以下视频来自亚利桑那州立大学 W. P. Carey 商学院,详细介绍了供应链的概念及其管理方式。
[![什么是供应链管理?来自亚利桑那州立大学 W. P. Carey 商学院的视频](https://img.youtube.com/vi/Mi1QBxVjZAw/0.jpg)](https://www.youtube.com/watch?v=Mi1QBxVjZAw)
> 🎥 点击上方图片观看视频
添加物联网设备可以显著改善供应链管理,使您能够更好地管理物品的位置、规划运输和货物处理,并更快地应对问题。
在管理车队(如卡车)时,了解每辆车在特定时间的位置非常有帮助。车辆可以安装 GPS 传感器,将其位置发送到物联网系统,使车主能够精确定位车辆位置、查看行驶路线,并了解车辆何时到达目的地。大多数车辆在 WiFi 覆盖范围之外运行因此它们使用蜂窝网络发送此类数据。有时GPS 传感器内置于更复杂的物联网设备中,例如电子日志。这些设备跟踪卡车的运输时间,以确保司机遵守当地关于工作时间的法律。
在本课中,您将学习如何使用全球定位系统 (GPS) 传感器追踪车辆位置。
本课内容包括:
* [联网车辆](../../../../../3-transport/lessons/1-location-tracking)
* [地理空间坐标](../../../../../3-transport/lessons/1-location-tracking)
* [全球定位系统 (GPS)](../../../../../3-transport/lessons/1-location-tracking)
* [读取 GPS 传感器数据](../../../../../3-transport/lessons/1-location-tracking)
* [NMEA GPS 数据](../../../../../3-transport/lessons/1-location-tracking)
* [解码 GPS 传感器数据](../../../../../3-transport/lessons/1-location-tracking)
## 联网车辆
物联网正在通过创建*联网车辆*车队改变货物运输方式。这些车辆连接到中央 IT 系统,报告其位置和其他传感器数据。拥有联网车辆车队有许多好处:
* 位置追踪 - 您可以随时精确定位车辆位置,从而:
* 在车辆即将到达目的地时收到警报,以便准备卸货人员
* 定位被盗车辆
* 将位置和路线数据与交通问题结合起来,使您能够在旅途中重新规划车辆路线
* 遵守税收规定。一些国家根据车辆在公共道路上行驶的里程数收取费用(例如 [新西兰的 RUC](https://www.nzta.govt.nz/vehicles/licensing-rego/road-user-charges/)),因此了解车辆在公共道路与私人道路上的行驶情况可以更轻松地计算应缴税款。
* 在车辆发生故障时知道派遣维修人员的位置
* 驾驶员遥测 - 确保驾驶员遵守限速规定、以适当速度转弯、提前有效刹车并安全驾驶。联网车辆还可以配备摄像头记录事故。这可以与保险挂钩,为优秀驾驶员提供优惠费率。
* 驾驶员工作时间合规性 - 确保驾驶员仅在法律允许的时间内驾驶车辆,基于发动机启动和关闭的时间。
这些好处可以结合使用,例如,将驾驶员工作时间合规性与位置追踪结合起来,如果驾驶员无法在允许的驾驶时间内到达目的地,则重新规划路线。这些还可以与其他车辆特定的遥测数据结合使用,例如温控卡车的温度数据,如果当前路线会导致货物无法保持适当温度,则重新规划车辆路线。
> 🎓 物流是将货物从一个地方运输到另一个地方的过程,例如从农场通过一个或多个仓库运输到超市。农民将西红柿装箱,装载到卡车上,运送到中央仓库,然后装载到可能包含不同类型农产品的第二辆卡车上,最终送到超市。
车辆追踪的核心组件是 GPS——能够在地球上任何地方定位其位置的传感器。在本课中您将学习如何使用 GPS 传感器,从了解如何定义地球上的位置开始。
## 地理空间坐标
地理空间坐标用于定义地球表面上的点,类似于如何使用坐标在计算机屏幕上绘制像素或在十字绣中定位针脚。对于单个点,您需要一对坐标。例如,美国华盛顿州雷德蒙市的微软园区位于 47.6423109, -122.1390293。
### 纬度和经度
地球是一个球体——一个三维圆。因此,点的定义是将其分为 360 度,与圆的几何形状相同。纬度测量从北到南的度数,经度测量从东到西的度数。
> 💁 没有人确切知道为什么圆被分为 360 度。[维基百科上的“角度 (度)”页面](https://wikipedia.org/wiki/Degree_(angle))介绍了一些可能的原因。
![纬度线从北极的 90°到北极和赤道之间的中点 45°到赤道的 0°到赤道和南极之间的中点 -45°到南极的 -90°](../../../../../translated_images/zh-CN/latitude-lines.11d8d91dfb2014a57437272d7db7fd6607243098e8685f06e0c5f1ec984cb7eb.png)
纬度是通过围绕地球的线测量的,这些线与赤道平行,将北半球和南半球分别分为 90°。赤道为 0°北极为 90°也称为北纬 90°南极为 -90°或南纬 90°。
经度是测量东西方向的度数。经度的 0° 起点称为*本初子午线*1884 年定义为穿过[英国格林威治皇家天文台](https://wikipedia.org/wiki/Royal_Observatory,_Greenwich)的一条从北极到南极的线。
![经度线从本初子午线以西的 -180°到本初子午线的 0°到本初子午线以东的 180°](../../../../../translated_images/zh-CN/longitude-meridians.ab4ef1c91c064586b0185a3c8d39e585903696c6a7d28c098a93a629cddb5d20.png)
> 🎓 子午线是从北极到南极的一条假想直线,形成一个半圆。
要测量某点的经度,您需要测量从本初子午线沿赤道到穿过该点的子午线的度数。经度范围从 -180°或西经 180°经过本初子午线的 0°到 180°或东经 180°。180° 和 -180° 指的是同一个点,即反子午线或第 180 条子午线。这是地球上与本初子午线相对的一条子午线。
> 💁 反子午线不要与国际日期变更线混淆,后者大致位于同一位置,但不是一条直线,并根据地缘政治边界有所变化。
✅ 做一些研究:尝试找到您当前所在位置的纬度和经度。
### 度、分和秒与十进制度
传统上,纬度和经度的度数测量使用六十进制或基数 60 的计数系统,这是古巴比伦人使用的计数系统,他们最早测量和记录时间与距离。您可能每天都在使用六十进制而没有意识到——将小时分为 60 分钟,将分钟分为 60 秒。
经度和纬度以度、分和秒为单位测量,其中 1 分为 1/60 度1 秒为 1/60 分。
例如,在赤道上:
* 1° 纬度为 **111.3 公里**
* 1 分纬度为 111.3/60 = **1.855 公里**
* 1 秒纬度为 1.855/60 = **0.031 公里**
分的符号是单引号秒的符号是双引号。例如2 度 17 分 43 秒可以写作 2°17'43"。秒的小数部分以小数表示,例如半秒为 0°0'0.5"。
计算机不使用六十进制,因此在大多数计算机系统中使用 GPS 数据时这些坐标以十进制度表示。例如2°17'43" 为 2.295277。通常省略度符号。
点的坐标总是以 `纬度, 经度` 的形式给出,因此前面提到的微软园区 47.6423109,-122.117198 的坐标为:
* 纬度为 47.6423109(北纬 47.6423109 度)
* 经度为 -122.1390293(西经 122.1390293 度)。
![微软园区位于 47.6423109,-122.117198](../../../../../translated_images/zh-CN/microsoft-gps-location-world.a321d481b010f6adfcca139b2ba0adc53b79f58a540495b8e2ce7f779ea64bfe.png)
## 全球定位系统 (GPS)
GPS 系统使用多个绕地球运行的卫星来定位您的位置。您可能已经在不知不觉中使用过 GPS 系统——例如在手机上的地图应用中找到您的位置(如 Apple Maps 或 Google Maps或者在叫车应用如 Uber 或 Lyft中查看您的车辆位置或者在汽车中使用卫星导航 (sat-nav)。
> 🎓 “卫星导航”中的卫星就是 GPS 卫星!
GPS 系统通过多个卫星发送信号,每个卫星包含其当前位置和精确时间戳。这些信号通过无线电波发送,并由 GPS 传感器中的天线检测到。GPS 传感器会检测这些信号并使用当前时间测量信号从卫星到传感器的传输时间。由于无线电波的速度是恒定的GPS 传感器可以使用发送的时间戳计算传感器与卫星之间的距离。通过结合至少 3 颗卫星发送的位置数据GPS 传感器能够精确定位其在地球上的位置。
> 💁 GPS 传感器需要天线来检测无线电波。内置 GPS 的卡车和汽车的天线通常安装在挡风玻璃或车顶上,以获得良好的信号。如果您使用单独的 GPS 系统,例如智能手机或物联网设备,则需要确保 GPS 系统或手机内置的天线能够清晰地看到天空,例如安装在挡风玻璃上。
![通过知道传感器与多个卫星的距离,可以计算出位置](../../../../../translated_images/zh-CN/gps-satellites.04acf1148fe25fbf1586bc2e8ba698e8d79b79a50c36824b38417dd13372b90f.png)
GPS 卫星绕地球运行,并非固定在传感器上方,因此位置数据包括海拔高度以及纬度和经度。
GPS 曾经受到美国军方强制限制,精度约为 5 米。该限制在 2000 年被取消,使精度达到 30 厘米。由于信号干扰,获得这种精度并不总是可能。
✅ 如果您有智能手机,打开地图应用程序,查看您的位置精度。可能需要一段时间让手机检测到多个卫星以获得更准确的位置。
💁 卫星上配备了极其精确的原子钟但由于爱因斯坦的狭义和广义相对论预测的速度增加导致时间变慢的效应与地球上的原子钟相比它们每天会漂移38微秒0.0000038秒——因为卫星的运行速度比地球自转速度更快。这种漂移现象已经被用来验证狭义和广义相对论的预测并且在设计GPS系统时必须进行调整。可以说GPS卫星上的时间确实运行得更慢。
全球定位系统GPS已被多个国家和政治联盟开发和部署包括美国、俄罗斯、日本、印度、欧盟和中国。现代GPS传感器可以连接到这些系统中的大多数以获得更快、更准确的定位。
> 🎓 每个部署中的卫星组被称为星座。
## 读取GPS传感器数据
大多数GPS传感器通过UART发送GPS数据。
> ⚠️ UART在[项目2课程2](../../../2-farm/lessons/2-detect-soil-moisture/README.md#universal-asynchronous-receiver-transmitter-uart)中已经介绍过。如果需要,可以回顾该课程。
您可以在物联网设备上使用GPS传感器来获取GPS数据。
### 任务 - 连接GPS传感器并读取GPS数据
按照相关指南使用您的物联网设备读取GPS数据
* [Arduino - Wio Terminal](wio-terminal-gps-sensor.md)
* [单板计算机 - Raspberry Pi](pi-gps-sensor.md)
* [单板计算机 - 虚拟设备](virtual-device-gps-sensor.md)
## NMEA GPS数据
运行代码时您可能会在输出中看到一些看似乱码的内容。实际上这些是标准的GPS数据每一部分都有其意义。
GPS传感器使用NMEA消息输出数据遵循NMEA 0183标准。NMEA是[国家海洋电子协会](https://www.nmea.org)的缩写,这是一个总部位于美国的贸易组织,负责制定海洋电子设备之间通信的标准。
> 💁 该标准是专有的售价至少为2000美元但由于有足够的信息公开大部分标准已经被逆向工程可以用于开源和其他非商业代码。
这些消息是基于文本的。每条消息由一个以`$`字符开头的*句子*组成后面是两个字符表示消息来源例如美国GPS系统为GP俄罗斯GPS系统GLONASS为GN以及三个字符表示消息类型。消息的其余部分是以逗号分隔的字段以换行符结束。
一些可以接收到的消息类型包括:
| 类型 | 描述 |
| ---- | ----------- |
| GGA | GPS定位数据包括GPS传感器的纬度、经度和海拔以及用于计算定位的可见卫星数量。 |
| ZDA | 当前日期和时间,包括当地时区。 |
| GSV | 可见卫星的详细信息——定义为GPS传感器可以检测到信号的卫星。 |
> 💁 GPS数据包括时间戳因此如果需要您的物联网设备可以从GPS传感器获取时间而无需依赖NTP服务器或内部实时时钟。
GGA消息使用`(dd)dmm.mmmm`格式提供当前位置,并附带一个字符表示方向。格式中的`d`表示度,`m`表示分秒以分的小数形式表示。例如2°17'43"将表示为217.716666667——2度17.716666667分。
方向字符可以是`N`或`S`表示纬度的北或南,`E`或`W`表示经度的东或西。例如纬度2°17'43"的方向字符为`N`,而-2°17'43"的方向字符为`S`。
例如 - NMEA句子`$GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67`
* 纬度部分是`4738.538654,N`转换为十进制度数为47.6423109。`4738.538654`表示47.6423109,方向为`N`(北),因此是正纬度。
* 经度部分是`12208.341758,W`,转换为十进制度数为-122.1390293。`12208.341758`表示122.1390293°,方向为`W`(西),因此是负经度。
## 解码GPS传感器数据
与其使用原始的NMEA数据不如将其解码为更有用的格式。您可以使用多个开源库来帮助从原始NMEA消息中提取有用的数据。
### 任务 - 解码GPS传感器数据
按照相关指南使用您的物联网设备解码GPS传感器数据
* [Arduino - Wio Terminal](wio-terminal-gps-decode.md)
* [单板计算机 - Raspberry Pi/虚拟物联网设备](single-board-computer-gps-decode.md)
---
## 🚀 挑战
自己编写一个NMEA解码器与其依赖第三方库解码NMEA句子您能否编写自己的解码器从NMEA句子中提取纬度和经度
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/22)
## 复习与自学
* 在[维基百科地理坐标系统页面](https://wikipedia.org/wiki/Geographic_coordinate_system)上阅读更多关于地理坐标的信息。
* 在[维基百科本初子午线页面](https://wikipedia.org/wiki/Prime_meridian#Prime_meridian_on_other_planetary_bodies)上了解地球以外其他天体的本初子午线。
* 研究由欧盟、日本、俄罗斯、印度和美国等世界各国政府和政治联盟开发的各种GPS系统。
## 作业
[调查其他GPS数据](assignment.md)
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,18 @@
# 调查其他 GPS 数据
## 指南
从您的 GPS 传感器接收到的 NMEA 语句中,除了位置信息外,还包含其他数据。请调查这些额外的数据,并将其应用到您的物联网设备中。
例如——您能获取当前的日期和时间吗?如果您使用的是微控制器,是否可以像在上一个项目中使用 NTP 信号设置时钟一样,使用 GPS 数据来设置时钟?您能获取海拔高度(海平面以上的高度)或当前速度吗?
如果您使用的是虚拟物联网设备,那么可以通过使用工具 [nmeagen.org](https://www.nmeagen.org) 生成的 NMEA 语句来获取其中的一些数据。
## 评分标准
| 标准 | 卓越 | 合格 | 需要改进 |
| ---- | ---- | ---- | -------- |
| 获取更多 GPS 数据 | 能够获取并使用更多 GPS 数据,无论是作为遥测数据还是用于设置物联网设备 | 能够获取更多 GPS 数据,但无法使用这些数据 | 无法获取更多 GPS 数据 |
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。对于因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,182 @@
# 读取 GPS 数据 - 树莓派
在本节课程中,您将为树莓派添加一个 GPS 传感器,并从中读取数据。
## 硬件
树莓派需要一个 GPS 传感器。
您将使用的传感器是 [Grove GPS Air530 传感器](https://www.seeedstudio.com/Grove-GPS-Air530-p-4584.html)。该传感器可以连接到多个 GPS 系统,以实现快速、准确的定位。传感器由两部分组成——传感器的核心电子部分,以及通过细线连接的外部天线,用于接收来自卫星的无线电波。
这是一个 UART 传感器,因此通过 UART 发送 GPS 数据。
## 连接 GPS 传感器
Grove GPS 传感器可以连接到树莓派。
### 任务 - 连接 GPS 传感器
连接 GPS 传感器。
![一个 Grove GPS 传感器](../../../../../translated_images/zh-CN/grove-gps-sensor.247943bf69b03f0d1820ef6ed10c587f9b650e8db55b936851c92412180bd3e2.png)
1. 将 Grove 电缆的一端插入 GPS 传感器上的插座。它只能以一种方式插入。
1. 在树莓派断电的情况下,将 Grove 电缆的另一端连接到树莓派上 Grove Base Hat 的 **UART** 插座。该插座位于中间一排,靠近 SD 卡插槽的一侧,与 USB 端口和以太网插座相对。
![Grove GPS 传感器连接到 UART 插座](../../../../../translated_images/zh-CN/pi-gps-sensor.1f99ee2b2f6528915047ec78967bd362e0e4ee0ed594368a3837b9cf9cdaca64.png)
1. 将 GPS 传感器放置好,使连接的天线能够看到天空——理想情况下靠近窗户或在室外。天线周围没有障碍物时,信号会更清晰。
## 编程 GPS 传感器
现在可以为树莓派编程以使用连接的 GPS 传感器。
### 任务 - 编程 GPS 传感器
为设备编程。
1. 启动树莓派并等待其启动完成。
1. GPS 传感器有两个 LED 指示灯——一个蓝色 LED当数据传输时会闪烁一个绿色 LED当从卫星接收数据时每秒闪烁一次。启动树莓派时确保蓝色 LED 闪烁。几分钟后,绿色 LED 会开始闪烁——如果没有,可能需要重新调整天线的位置。
1. 启动 VS Code可以直接在树莓派上运行也可以通过 Remote SSH 扩展连接。
> ⚠️ 如果需要,可以参考[第 1 课中设置和启动 VS Code 的说明](../../../1-getting-started/lessons/1-introduction-to-iot/pi.md)。
1. 对于支持蓝牙的新版本树莓派,蓝牙使用的串口与 Grove UART 端口使用的串口存在冲突。为了解决这个问题,请执行以下操作:
1. 在 VS Code 的终端中,使用以下命令编辑 `/boot/config.txt` 文件。`nano` 是一个内置的终端文本编辑器:
```sh
sudo nano /boot/config.txt
```
> 由于需要 `sudo` 权限(提升的权限)才能编辑此文件,因此无法通过 VS Code 编辑。
1. 使用光标键导航到文件末尾,然后将以下代码复制并粘贴到文件末尾:
```ini
dtoverlay=pi3-miniuart-bt
dtoverlay=pi3-disable-bt
enable_uart=1
```
您可以使用设备的常规键盘快捷键粘贴Windows、Linux 或树莓派 OS 上为 `Ctrl+v`macOS 上为 `Cmd+v`)。
1. 按 `Ctrl+x` 保存文件并退出 nano。当被询问是否保存修改的缓冲区时`y`,然后按 `Enter` 确认覆盖 `/boot/config.txt`
> 如果出错,可以不保存退出,然后重复这些步骤。
1. 使用以下命令在 nano 中编辑 `/boot/cmdline.txt` 文件:
```sh
sudo nano /boot/cmdline.txt
```
1. 此文件包含多个用空格分隔的键值对。删除任何键为 `console` 的键值对。它们可能看起来像这样:
```output
console=serial0,115200 console=tty1
```
您可以使用光标键导航到这些条目,然后使用常规的 `del``backspace` 键删除。
例如,如果您的原始文件如下所示:
```output
console=serial0,115200 console=tty1 root=PARTUUID=058e2867-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
```
新版本将如下所示:
```output
root=PARTUUID=058e2867-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
```
1. 按上述步骤保存此文件并退出 nano。
1. 重启树莓派,重启后重新连接到 VS Code。
1. 在终端中,在 `pi` 用户的主目录中创建一个名为 `gps-sensor` 的新文件夹。在此文件夹中创建一个名为 `app.py` 的文件。
1. 在 VS Code 中打开此文件夹。
1. GPS 模块通过串口发送 UART 数据。安装 `pyserial` Pip 包,以便从 Python 代码与串口通信:
```sh
pip3 install pyserial
```
1. 将以下代码添加到您的 `app.py` 文件中:
```python
import time
import serial
serial = serial.Serial('/dev/ttyAMA0', 9600, timeout=1)
serial.reset_input_buffer()
serial.flush()
def print_gps_data(line):
print(line.rstrip())
while True:
line = serial.readline().decode('utf-8')
while len(line) > 0:
print_gps_data(line)
line = serial.readline().decode('utf-8')
time.sleep(1)
```
这段代码从 `pyserial` Pip 包中导入了 `serial` 模块。然后它连接到 `/dev/ttyAMA0` 串口——这是 Grove Pi Base Hat 用于其 UART 端口的串口地址。接着,它清除了此串口连接中的任何现有数据。
接下来定义了一个名为 `print_gps_data` 的函数,用于将传递给它的行打印到控制台。
然后代码进入一个无限循环,在每次循环中读取串口中的多行文本。它为每一行调用 `print_gps_data` 函数。
读取完所有数据后,循环会休眠 1 秒,然后再次尝试。
1. 运行此代码。您将看到来自 GPS 传感器的原始输出,类似如下内容:
```output
$GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$BDGSA,A,1,,,,,,,,,,,,,,,*0F
$GPGSV,1,1,00*79
$BDGSV,1,1,00*68
```
> 如果在停止和重新启动代码时遇到以下错误之一,请在 while 循环中添加一个 `try - except` 块。
```output
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x93 in position 0: invalid start byte
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf1 in position 0: invalid continuation byte
```
```python
while True:
try:
line = serial.readline().decode('utf-8')
while len(line) > 0:
print_gps_data()
line = serial.readline().decode('utf-8')
# There's a random chance the first byte being read is part way through a character.
# Read another full line and continue.
except UnicodeDecodeError:
line = serial.readline().decode('utf-8')
time.sleep(1)
```
> 💁 您可以在 [code-gps/pi](../../../../../3-transport/lessons/1-location-tracking/code-gps/pi) 文件夹中找到此代码。
😀 恭喜!您的 GPS 传感器程序运行成功!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,64 @@
# 解码GPS数据 - 虚拟物联网硬件和树莓派
在本节课程中您将解码树莓派或虚拟物联网设备从GPS传感器读取的NMEA消息并提取纬度和经度。
## 解码GPS数据
一旦从串口读取到原始NMEA数据就可以使用开源NMEA库进行解码。
### 任务 - 解码GPS数据
编程设备以解码GPS数据。
1. 如果尚未打开,请打开 `gps-sensor` 应用项目
1. 安装 `pynmea2` Pip包。此包包含用于解码NMEA消息的代码。
```sh
pip3 install pynmea2
```
1. 在 `app.py` 文件的导入部分添加以下代码以导入 `pynmea2` 模块:
```python
import pynmea2
```
1. 用以下内容替换 `print_gps_data` 函数的内容:
```python
msg = pynmea2.parse(line)
if msg.sentence_type == 'GGA':
lat = pynmea2.dm_to_sd(msg.lat)
lon = pynmea2.dm_to_sd(msg.lon)
if msg.lat_dir == 'S':
lat = lat * -1
if msg.lon_dir == 'W':
lon = lon * -1
print(f'{lat},{lon} - from {msg.num_sats} satellites')
```
此代码将使用 `pynmea2` 库解析从UART串口读取的行。
如果消息的句子类型是 `GGA`那么这是一条位置固定消息并将被处理。纬度和经度值将从消息中读取并从NMEA `(d)ddmm.mmmm` 格式转换为十进制度。`dm_to_sd` 函数完成此转换。
然后检查纬度的方向,如果纬度是南方,则将其值转换为负数。同样地,如果经度是西方,则将其转换为负数。
最后,坐标将打印到控制台,同时显示用于定位的卫星数量。
1. 运行代码。如果您使用的是虚拟物联网设备请确保CounterFit应用正在运行并且GPS数据正在发送。
```output
pi@raspberrypi:~/gps-sensor $ python3 app.py
47.6423109,-122.1390293 - from 3 satellites
```
> 💁 您可以在 [code-gps-decode/virtual-device](../../../../../3-transport/lessons/1-location-tracking/code-gps-decode/virtual-device) 文件夹或 [code-gps-decode/pi](../../../../../3-transport/lessons/1-location-tracking/code-gps-decode/pi) 文件夹中找到此代码。
😀 您的GPS传感器程序成功解码数据
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,133 @@
# 读取 GPS 数据 - 虚拟物联网硬件
在本课程的这一部分,你将为虚拟物联网设备添加一个 GPS 传感器,并从中读取数据。
## 虚拟硬件
虚拟物联网设备将使用一个通过串口 UART 访问的模拟 GPS 传感器。
物理 GPS 传感器通常配备天线,用于接收来自 GPS 卫星的无线电波,并将 GPS 信号转换为 GPS 数据。虚拟版本通过以下方式进行模拟:你可以设置纬度和经度、发送原始 NMEA 语句,或者上传包含多个位置的 GPX 文件,这些位置将按顺序返回。
> 🎓 NMEA 语句将在本课程后续部分介绍
### 将传感器添加到 CounterFit
要使用虚拟 GPS 传感器,你需要将其添加到 CounterFit 应用中。
#### 任务 - 将传感器添加到 CounterFit
将 GPS 传感器添加到 CounterFit 应用。
1. 在你的电脑上创建一个名为 `gps-sensor` 的文件夹,并在其中创建一个新的 Python 应用,包含一个名为 `app.py` 的文件和一个 Python 虚拟环境,然后添加 CounterFit 的 pip 包。
> ⚠️ 如果需要,可以参考[课程 1 中关于创建和设置 CounterFit Python 项目的说明](../../../1-getting-started/lessons/1-introduction-to-iot/virtual-device.md)。
1. 安装一个额外的 Pip 包,用于安装一个 CounterFit shim可以通过串口与基于 UART 的传感器通信。确保在激活虚拟环境的终端中安装此包。
```sh
pip install counterfit-shims-serial
```
1. 确保 CounterFit 网页应用正在运行。
1. 创建一个 GPS 传感器:
1. 在 *Sensors* 面板的 *Create sensor* 框中,点击 *Sensor type* 下拉框并选择 *UART GPS*
1. 将 *Port* 保持为 */dev/ttyAMA0*
1. 点击 **Add** 按钮,在端口 `/dev/ttyAMA0` 上创建 GPS 传感器。
![GPS 传感器设置](../../../../../translated_images/zh-CN/counterfit-create-gps-sensor.6385dc9357d85ad1d47b4abb2525e7651fd498917d25eefc5a72feab09eedc70.png)
GPS 传感器将被创建并显示在传感器列表中。
![已创建的 GPS 传感器](../../../../../translated_images/zh-CN/counterfit-gps-sensor.3fbb15af0a5367566f2f11324ef5a6f30861cdf2b497071a5e002b7aa473550e.png)
## 编程 GPS 传感器
现在可以为虚拟物联网设备编程以使用虚拟 GPS 传感器。
### 任务 - 编程 GPS 传感器
编写 GPS 传感器应用程序。
1. 确保 `gps-sensor` 应用已在 VS Code 中打开。
1. 打开 `app.py` 文件。
1. 在 `app.py` 文件顶部添加以下代码,将应用连接到 CounterFit
```python
from counterfit_connection import CounterFitConnection
CounterFitConnection.init('127.0.0.1', 5000)
```
1. 在此代码下方添加以下代码,导入一些所需的库,包括用于 CounterFit 串口的库:
```python
import time
import counterfit_shims_serial
serial = counterfit_shims_serial.Serial('/dev/ttyAMA0')
```
此代码从 `counterfit_shims_serial` Pip 包中导入 `serial` 模块。然后连接到 `/dev/ttyAMA0` 串口——这是虚拟 GPS 传感器用于其 UART 端口的地址。
1. 在此代码下方添加以下代码,从串口读取数据并将值打印到控制台:
```python
def print_gps_data(line):
print(line.rstrip())
while True:
line = serial.readline().decode('utf-8')
while len(line) > 0:
print_gps_data(line)
line = serial.readline().decode('utf-8')
time.sleep(1)
```
定义了一个名为 `print_gps_data` 的函数,用于将传入的行打印到控制台。
接下来,代码进入一个无限循环,在每次循环中尽可能多地从串口读取文本行。它为每一行调用 `print_gps_data` 函数。
读取完所有数据后,循环会休眠 1 秒,然后再次尝试。
1. 运行此代码,确保使用与运行 CounterFit 应用不同的终端,以便 CounterFit 应用保持运行状态。
1. 在 CounterFit 应用中更改 GPS 传感器的值。你可以通过以下方式之一进行更改:
* 将 **Source** 设置为 `Lat/Lon`,并设置明确的纬度、经度以及用于获取 GPS 定位的卫星数量。此值将仅发送一次,因此勾选 **Repeat** 复选框以使数据每秒重复发送。
![选择纬度和经度的 GPS 传感器](../../../../../translated_images/zh-CN/counterfit-gps-sensor-latlon.008c867d75464fbe7f84107cc57040df565ac07cb57d2f21db37d087d470197d.png)
* 将 **Source** 设置为 `NMEA`,并在文本框中添加一些 NMEA 语句。所有这些值将被发送,每个新的 GGA位置固定语句之间有 1 秒的延迟。
![设置 NMEA 语句的 GPS 传感器](../../../../../translated_images/zh-CN/counterfit-gps-sensor-nmea.c62eea442171e17e19528b051b104cfcecdc9cd18db7bc72920f29821ae63f73.png)
你可以使用类似 [nmeagen.org](https://www.nmeagen.org) 的工具通过在地图上绘制来生成这些语句。这些值将仅发送一次,因此勾选 **Repeat** 复选框以使数据在全部发送后每秒重复一次。
* 将 **Source** 设置为 GPX 文件,并上传一个包含轨迹位置的 GPX 文件。你可以从许多流行的地图和徒步网站(如 [AllTrails](https://www.alltrails.com/))下载 GPX 文件。这些文件包含多个 GPS 位置作为轨迹GPS 传感器将以 1 秒间隔返回每个新位置。
![设置 GPX 文件的 GPS 传感器](../../../../../translated_images/zh-CN/counterfit-gps-sensor-gpxfile.8310b063ce8a425ccc8ebeec8306aeac5e8e55207f007d52c6e1194432a70cd9.png)
这些值将仅发送一次,因此勾选 **Repeat** 复选框以使数据在全部发送后每秒重复一次。
配置 GPS 设置后,点击 **Set** 按钮将这些值提交到传感器。
1. 你将看到来自 GPS 传感器的原始输出,类似如下:
```output
$GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67
$GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67
```
> 💁 你可以在 [code-gps/virtual-device](../../../../../3-transport/lessons/1-location-tracking/code-gps/virtual-device) 文件夹中找到此代码。
😀 恭喜!你的 GPS 传感器程序运行成功!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,72 @@
# 解码 GPS 数据 - Wio Terminal
在本节课程中,您将解码从 Wio Terminal 的 GPS 传感器读取的 NMEA 消息,并提取纬度和经度。
## 解码 GPS 数据
一旦从串口读取到原始的 NMEA 数据,就可以使用开源的 NMEA 库对其进行解码。
### 任务 - 解码 GPS 数据
编程设备以解码 GPS 数据。
1. 如果尚未打开 `gps-sensor` 应用项目,请将其打开。
1. 在项目的 `platformio.ini` 文件中添加 [TinyGPSPlus](https://github.com/mikalhart/TinyGPSPlus) 库的依赖项。该库包含解码 NMEA 数据的代码。
```ini
lib_deps =
mikalhart/TinyGPSPlus @ 1.0.2
```
1. 在 `main.cpp` 中,为 TinyGPSPlus 库添加一个 include 指令:
```cpp
#include <TinyGPS++.h>
```
1. 在 `Serial3` 声明的下方,声明一个 TinyGPSPlus 对象,用于处理 NMEA 语句:
```cpp
TinyGPSPlus gps;
```
1. 将 `printGPSData` 函数的内容更改为以下内容:
```cpp
if (gps.encode(Serial3.read()))
{
if (gps.location.isValid())
{
Serial.print(gps.location.lat(), 6);
Serial.print(F(","));
Serial.print(gps.location.lng(), 6);
Serial.print(" - from ");
Serial.print(gps.satellites.value());
Serial.println(" satellites");
}
}
```
这段代码从 UART 串口读取下一个字符到 `gps` NMEA 解码器中。每读取一个字符后,它会检查解码器是否读取到有效的语句,然后检查是否读取到有效的位置。如果位置有效,它会将其发送到串口监视器,同时显示参与定位的卫星数量。
1. 构建并上传代码到 Wio Terminal。
1. 上传完成后,您可以使用串口监视器查看 GPS 位置信息。
```output
> Executing task: platformio device monitor <
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at http://bit.ly/pio-monitor-filters
--- Miniterm on /dev/cu.usbmodem1201 9600,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
47.6423109,-122.1390293 - from 3 satellites
```
> 💁 您可以在 [code-gps-decode/wio-terminal](../../../../../3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal) 文件夹中找到这段代码。
😀 您的 GPS 传感器程序成功实现了数据解码!
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,143 @@
# 读取 GPS 数据 - Wio Terminal
在本课的这一部分中,您将为 Wio Terminal 添加一个 GPS 传感器,并读取其数据。
## 硬件
Wio Terminal 需要一个 GPS 传感器。
您将使用的传感器是 [Grove GPS Air530 传感器](https://www.seeedstudio.com/Grove-GPS-Air530-p-4584.html)。该传感器可以连接到多个 GPS 系统,以实现快速、准确的定位。传感器由两部分组成——传感器的核心电子部分,以及通过一根细线连接的外部天线,用于接收来自卫星的无线电波。
这是一个 UART 传感器,因此通过 UART 发送 GPS 数据。
### 连接 GPS 传感器
Grove GPS 传感器可以连接到 Wio Terminal。
#### 任务 - 连接 GPS 传感器
连接 GPS 传感器。
![Grove GPS 传感器](../../../../../translated_images/zh-CN/grove-gps-sensor.247943bf69b03f0d1820ef6ed10c587f9b650e8db55b936851c92412180bd3e2.png)
1. 将 Grove 电缆的一端插入 GPS 传感器上的插座。电缆只能以一种方向插入。
1. 在 Wio Terminal 未连接到计算机或其他电源的情况下,将 Grove 电缆的另一端连接到 Wio Terminal 屏幕左侧的 Grove 插座。这是靠近电源按钮的插座。
![Grove GPS 传感器连接到左侧插座](../../../../../translated_images/zh-CN/wio-gps-sensor.19fd52b81ce58095.webp)
1. 将 GPS 传感器放置在附带天线可以看到天空的位置——理想情况下靠近窗户或在室外。天线周围没有障碍物时,更容易获得清晰的信号。
1. 现在可以将 Wio Terminal 连接到您的计算机。
1. GPS 传感器有两个 LED 指示灯——蓝色 LED 在传输数据时闪烁,绿色 LED 在接收到来自卫星的数据时每秒闪烁一次。确保在 Wio Terminal 通电时蓝色 LED 闪烁。几分钟后,绿色 LED 会开始闪烁——如果没有,您可能需要重新调整天线的位置。
## 编程 GPS 传感器
现在可以为 Wio Terminal 编写程序,以使用连接的 GPS 传感器。
### 任务 - 编程 GPS 传感器
为设备编写程序。
1. 使用 PlatformIO 创建一个全新的 Wio Terminal 项目。将该项目命名为 `gps-sensor`。在 `setup` 函数中添加代码以配置串口。
1. 在 `main.cpp` 文件顶部添加以下 include 指令。这将包含一个头文件,其中包含用于配置左侧 Grove 端口为 UART 的函数。
```cpp
#include <wiring_private.h>
```
1. 在此之后,添加以下代码行以声明一个与 UART 端口的串口连接:
```cpp
static Uart Serial3(&sercom3, PIN_WIRE_SCL, PIN_WIRE_SDA, SERCOM_RX_PAD_1, UART_TX_PAD_0);
```
1. 您需要添加一些代码,将一些内部信号处理程序重定向到该串口。将以下代码添加到 `Serial3` 声明之后:
```cpp
void SERCOM3_0_Handler()
{
Serial3.IrqHandler();
}
void SERCOM3_1_Handler()
{
Serial3.IrqHandler();
}
void SERCOM3_2_Handler()
{
Serial3.IrqHandler();
}
void SERCOM3_3_Handler()
{
Serial3.IrqHandler();
}
```
1. 在 `setup` 函数中,在配置 `Serial` 端口的代码之后,使用以下代码配置 UART 串口:
```cpp
Serial3.begin(9600);
while (!Serial3)
; // Wait for Serial3 to be ready
delay(1000);
```
1. 在 `setup` 函数中的上述代码之后,添加以下代码以将 Grove 引脚连接到串口:
```cpp
pinPeripheral(PIN_WIRE_SCL, PIO_SERCOM_ALT);
```
1. 在 `loop` 函数之前,添加以下函数以将 GPS 数据发送到串口监视器:
```cpp
void printGPSData()
{
Serial.println(Serial3.readStringUntil('\n'));
}
```
1. 在 `loop` 函数中,添加以下代码以从 UART 串口读取数据并将输出打印到串口监视器:
```cpp
while (Serial3.available() > 0)
{
printGPSData();
}
delay(1000);
```
这段代码从 UART 串口读取数据。`readStringUntil` 函数会读取直到终止符(在本例中是换行符)为止的数据。这将读取整个 NMEA 语句NMEA 语句以换行符结束)。只要可以从 UART 串口读取数据,就会读取数据并通过 `printGPSData` 函数发送到串口监视器。一旦无法读取更多数据,`loop` 会延迟 1 秒1,000 毫秒)。
1. 构建并上传代码到 Wio Terminal。
1. 上传完成后,您可以使用串口监视器查看 GPS 数据。
```output
> Executing task: platformio device monitor <
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at http://bit.ly/pio-monitor-filters
--- Miniterm on /dev/cu.usbmodem1201 9600,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
$GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$BDGSA,A,1,,,,,,,,,,,,,,,*0F
$GPGSV,1,1,00*79
$BDGSV,1,1,00*68
```
> 💁 您可以在 [code-gps/wio-terminal](../../../../../3-transport/lessons/1-location-tracking/code-gps/wio-terminal) 文件夹中找到这段代码。
😀 恭喜!您的 GPS 传感器程序运行成功!
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,466 @@
# 存储位置信息数据
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-12.ca7f53039712a3ec14ad6474d8445361c84adab643edc53fa6269b77895606bb.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看大图。
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/23)
## 简介
在上一课中,你学习了如何使用 GPS 传感器捕获位置信息数据。为了将这些数据用于可视化一辆满载食物的卡车的位置及其行程,需要将数据发送到云端的 IoT 服务,并存储在某个地方。
在本课中,你将学习存储 IoT 数据的不同方法,并学习如何使用无服务器代码存储来自 IoT 服务的数据。
本课内容包括:
* [结构化数据和非结构化数据](../../../../../3-transport/lessons/2-store-location-data)
* [将 GPS 数据发送到 IoT Hub](../../../../../3-transport/lessons/2-store-location-data)
* [热路径、温路径和冷路径](../../../../../3-transport/lessons/2-store-location-data)
* [使用无服务器代码处理 GPS 事件](../../../../../3-transport/lessons/2-store-location-data)
* [Azure 存储账户](../../../../../3-transport/lessons/2-store-location-data)
* [将无服务器代码连接到存储](../../../../../3-transport/lessons/2-store-location-data)
## 结构化数据和非结构化数据
计算机系统处理数据,而这些数据的形状和大小各不相同。数据可以是单个数字、大量文本、视频和图像,或者 IoT 数据。通常,数据可以分为两类——*结构化数据* 和 *非结构化数据*
* **结构化数据** 是具有明确、固定结构的数据,通常映射到具有关系的数据表。例如,一个人的详细信息,包括姓名、出生日期和地址。
* **非结构化数据** 是没有明确、固定结构的数据,包括结构可能经常变化的数据。例如,文档如书面文件或电子表格。
✅ 进行一些研究:你能想到其他结构化和非结构化数据的例子吗?
> 💁 还有一种半结构化数据,它是有结构的,但不适合固定的数据表。
IoT 数据通常被认为是非结构化数据。
想象一下,你为一个大型商业农场的车队添加了 IoT 设备。你可能希望为不同类型的车辆使用不同的设备。例如:
* 对于像拖拉机这样的农用车辆,你需要 GPS 数据以确保它们在正确的田地上工作。
* 对于将食物运送到仓库的送货卡车,你需要 GPS 数据以及速度和加速度数据,以确保司机安全驾驶,还需要驾驶员身份和启动/停止数据,以确保司机遵守当地的工作时间法律。
* 对于冷藏卡车,你还需要温度数据,以确保食物在运输过程中不会过热或过冷而变质。
这些数据可能会不断变化。例如,如果 IoT 设备位于卡车驾驶室中,那么它发送的数据可能会随着挂车的变化而变化,例如只有在使用冷藏挂车时才发送温度数据。
✅ 还有哪些 IoT 数据可能会被捕获?想一想卡车可能运输的货物类型,以及维护数据。
这些数据因车辆而异,但它们都会被发送到同一个 IoT 服务进行处理。IoT 服务需要能够处理这些非结构化数据,并以一种既能搜索或分析,又能适应数据不同结构的方式存储它们。
### SQL 与 NoSQL 存储
数据库是允许你存储和查询数据的服务。数据库分为两种类型——SQL 和 NoSQL。
#### SQL 数据库
最早的数据库是关系型数据库管理系统RDBMS也称为关系型数据库。这些数据库也被称为 SQL 数据库因为它们使用结构化查询语言SQL来添加、删除、更新或查询数据。这些数据库由一个模式组成——一组定义良好的数据表类似于电子表格。每个表有多个命名列。当你插入数据时你会向表中添加一行将值放入每列中。这种方式使数据保持非常严格的结构——尽管你可以留空某些列但如果你想添加新列则必须在数据库中执行此操作并为现有行填充值。这些数据库是关系型的——一个表可以与另一个表有关系。
![一个关系型数据库,其中用户表的 ID 与购买表的用户 ID 列相关联,产品表的 ID 与购买表的产品 ID 相关联](../../../../../translated_images/zh-CN/sql-database.be160f12bfccefd3.webp)
例如,如果你在一个表中存储用户的个人详细信息,你会为每个用户分配某种内部唯一 ID该 ID 用于包含用户姓名和地址的表中的一行。如果你想在另一个表中存储该用户的其他详细信息,例如他们的购买记录,你会在新表中为该用户的 ID 添加一列。当你查找用户时,可以使用他们的 ID 从一个表中获取他们的个人详细信息,从另一个表中获取他们的购买记录。
SQL 数据库非常适合存储结构化数据,并且当你希望确保数据符合你的模式时非常有用。
✅ 如果你之前没有使用过 SQL请花点时间阅读 [Wikipedia 上的 SQL 页面](https://wikipedia.org/wiki/SQL)。
一些知名的 SQL 数据库包括 Microsoft SQL Server、MySQL 和 PostgreSQL。
✅ 进行一些研究:阅读这些 SQL 数据库及其功能。
#### NoSQL 数据库
NoSQL 数据库之所以被称为 NoSQL是因为它们没有 SQL 数据库的严格结构。它们也被称为文档数据库,因为它们可以存储非结构化数据,例如文档。
> 💁 尽管名字叫 NoSQL但一些 NoSQL 数据库允许你使用 SQL 查询数据。
![NoSQL 数据库中的文件夹中的文档](../../../../../translated_images/zh-CN/noqsl-database.62d24ccf5b73f60d35c245a8533f1c7147c0928e955b82cb290b2e184bb434df.png)
NoSQL 数据库没有预定义的模式来限制数据的存储方式,你可以插入任何非结构化数据,通常使用 JSON 文档。这些文档可以像计算机上的文件一样组织成文件夹。每个文档可以与其他文档有不同的字段——例如,如果你存储农用车辆的 IoT 数据,有些可能有加速度计和速度数据的字段,另一些可能有挂车温度的字段。如果你想添加一种新型卡车,例如带有内置秤以跟踪所载货物重量的卡车,那么你的 IoT 设备可以添加这个新字段,并且可以在无需更改数据库的情况下存储它。
一些知名的 NoSQL 数据库包括 Azure CosmosDB、MongoDB 和 CouchDB。
✅ 进行一些研究:阅读这些 NoSQL 数据库及其功能。
在本课中,你将使用 NoSQL 存储来存储 IoT 数据。
## 将 GPS 数据发送到 IoT Hub
在上一课中,你从连接到 IoT 设备的 GPS 传感器捕获了 GPS 数据。为了在云端存储这些 IoT 数据,你需要将其发送到 IoT 服务。你将再次使用 Azure IoT Hub这是你在前一个项目中使用的同一个 IoT 云服务。
![将 GPS 遥测数据从 IoT 设备发送到 IoT Hub](../../../../../translated_images/zh-CN/gps-telemetry-iot-hub.8115335d51cd2c1285d20e9d1b18cf685e59a8e093e7797291ef173445af6f3d.png)
### 任务 - 将 GPS 数据发送到 IoT Hub
1. 使用免费层创建一个新的 IoT Hub。
> ⚠️ 如果需要,可以参考 [项目 2第 4 课中创建 IoT Hub 的说明](../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md#create-an-iot-service-in-the-cloud)。
记得创建一个新的资源组。将新资源组命名为 `gps-sensor`,并为新的 IoT Hub 取一个基于 `gps-sensor` 的唯一名称,例如 `gps-sensor-<你的名字>`
> 💁 如果你仍然保留了前一个项目中的 IoT Hub可以重复使用它。记得在创建其他服务时使用该 IoT Hub 的名称及其所在的资源组。
1. 在 IoT Hub 中添加一个新设备。将此设备命名为 `gps-sensor`。获取该设备的连接字符串。
1. 更新你的设备代码,使用上一步中的设备连接字符串将 GPS 数据发送到新的 IoT Hub。
> ⚠️ 如果需要,可以参考 [项目 2第 4 课中将设备连接到 IoT 的说明](../../../2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md#connect-your-device-to-the-iot-service)。
1. 发送 GPS 数据时,请以以下 JSON 格式发送:
```json
{
"gps" :
{
"lat" : <latitude>,
"lon" : <longitude>
}
}
```
1. 每分钟发送一次 GPS 数据,以免用完每日消息配额。
如果你使用的是 Wio Terminal请记得添加所有必要的库并使用 NTP 服务器设置时间。你的代码还需要确保在发送 GPS 位置之前已从串口读取所有数据,使用上一课中的现有代码。使用以下代码构建 JSON 文档:
```cpp
DynamicJsonDocument doc(1024);
doc["gps"]["lat"] = gps.location.lat();
doc["gps"]["lon"] = gps.location.lng();
```
如果你使用的是虚拟 IoT 设备,请记得使用虚拟环境安装所有必要的库。
对于 Raspberry Pi 和虚拟 IoT 设备,请使用上一课中的现有代码获取纬度和经度值,然后使用以下代码以正确的 JSON 格式发送它们:
```python
message_json = { "gps" : { "lat":lat, "lon":lon } }
print("Sending telemetry", message_json)
message = Message(json.dumps(message_json))
```
> 💁 你可以在 [code/wio-terminal](../../../../../3-transport/lessons/2-store-location-data/code/wio-terminal)、[code/pi](../../../../../3-transport/lessons/2-store-location-data/code/pi) 或 [code/virtual-device](../../../../../3-transport/lessons/2-store-location-data/code/virtual-device) 文件夹中找到此代码。
运行你的设备代码,并使用 `az iot hub monitor-events` CLI 命令确保消息正在流入 IoT Hub。
## 热路径、温路径和冷路径
从 IoT 设备流向云端的数据并不总是实时处理的。有些数据需要实时处理,有些数据可以稍后处理,还有些数据可以更晚处理。数据流向不同服务以在不同时间处理的方式被称为热路径、温路径和冷路径。
### 热路径
热路径指需要实时或接近实时处理的数据。你可以使用热路径数据进行警报,例如当车辆接近仓库时,或者冷藏卡车的温度过高时发出警报。
要使用热路径数据,你的代码需要在云服务接收到事件时立即响应。
### 温路径
温路径指可以在接收到数据后稍作延迟再处理的数据,例如用于报告或短期分析。你可以使用温路径数据生成每日车辆里程报告,使用前一天收集的数据。
温路径数据在被云服务接收后会存储在某种可以快速访问的存储中。
### 冷路径
冷路径指历史数据,长期存储的数据可以在需要时处理。例如,你可以使用冷路径生成年度车辆里程报告,或者对路线进行分析以找到减少燃料成本的最优路线。
冷路径数据存储在数据仓库中——这些数据库专为存储大量不会更改的数据而设计,可以快速轻松地查询。通常,你的云应用程序会定期运行一个任务(每天、每周或每月)将数据从温路径存储移动到数据仓库。
✅ 回想一下你在这些课程中捕获的数据。它是热路径、温路径还是冷路径数据?
## 使用无服务器代码处理 GPS 事件
一旦数据流入你的 IoT Hub你可以编写一些无服务器代码来监听发布到 Event-Hub 兼容端点的事件。这是温路径——这些数据将被存储,并在下一课中用于报告行程。
![将 GPS 遥测数据从 IoT 设备发送到 IoT Hub然后通过事件中心触发器发送到 Azure Functions](../../../../../translated_images/zh-CN/gps-telemetry-iot-hub-functions.24d3fa5592455e9f4e2fe73856b40c3915a292b90263c31d652acfd976cfedd8.png)
### 任务 - 使用无服务器代码处理 GPS 事件
1. 使用 Azure Functions CLI 创建一个 Azure Functions 应用。使用 Python 运行时,并在名为 `gps-trigger` 的文件夹中创建它,同时将 Functions App 项目名称也命名为 `gps-trigger`。确保创建一个虚拟环境以供使用。
> ⚠️ 如果需要,可以参考[从项目 2第 5 课创建 Azure Functions 项目的说明](../../../2-farm/lessons/5-migrate-application-to-the-cloud/README.md#create-a-serverless-application)。
1. 添加一个 IoT Hub 事件触发器,使用 IoT Hub 的 Event Hub 兼容端点。
> ⚠️ 如果需要,可以参考[项目 2第 5 课中创建 IoT Hub 事件触发器的说明](../../../2-farm/lessons/5-migrate-application-to-the-cloud/README.md#create-an-iot-hub-event-trigger)。
1. 在 `local.settings.json` 文件中设置 Event Hub 兼容端点的连接字符串,并在 `function.json` 文件中使用该条目的键。
1. 使用 Azurite 应用作为本地存储模拟器。
1. 运行你的 Functions 应用,确保它能够接收来自 GPS 设备的事件。确保你的 IoT 设备也在运行并发送 GPS 数据。
```output
Python EventHub trigger processed an event: {"gps": {"lat": 47.73481, "lon": -122.25701}}
```
## Azure 存储账户
![Azure 存储标志](../../../../../translated_images/zh-CN/azure-storage-logo.605c0f602c640d482a80f1b35a2629a32d595711b7ab1d7ceea843250615ff32.png)
Azure 存储账户是一种通用存储服务,可以以多种方式存储数据。你可以将数据存储为 Blob、队列、表或文件并且可以同时使用这些方式。
### Blob 存储
*Blob* 这个词的意思是二进制大对象,但它已经成为任何非结构化数据的代名词。你可以将任何数据存储在 Blob 存储中,从包含 IoT 数据的 JSON 文档到图片和视频文件。Blob 存储有 *容器* 的概念,容器是命名的存储桶,用于存储数据,类似于关系数据库中的表。这些容器可以包含一个或多个文件夹来存储 Blob每个文件夹还可以包含其他文件夹类似于计算机硬盘上的文件存储方式。
在本课中,你将使用 Blob 存储来存储 IoT 数据。
✅ 做一些研究:阅读 [Azure Blob 存储](https://docs.microsoft.com/azure/storage/blobs/storage-blobs-overview?WT.mc_id=academic-17441-jabenn)
### 表存储
表存储允许你存储半结构化数据。表存储实际上是一个 NoSQL 数据库,因此不需要预先定义表集,但它被设计为在一个或多个表中存储数据,每行都有唯一的键。
✅ 做一些研究:阅读 [Azure 表存储](https://docs.microsoft.com/azure/storage/tables/table-storage-overview?WT.mc_id=academic-17441-jabenn)
### 队列存储
队列存储允许你在队列中存储大小最多为 64KB 的消息。你可以将消息添加到队列的末尾,并从队列的开头读取。只要存储空间足够,队列会无限期存储消息,因此可以长期存储消息,然后在需要时读取。例如,如果你想运行一个每月的任务来处理 GPS 数据,可以每天将数据添加到队列中,然后在月底处理队列中的所有消息。
✅ 做一些研究:阅读 [Azure 队列存储](https://docs.microsoft.com/azure/storage/queues/storage-queues-introduction?WT.mc_id=academic-17441-jabenn)
### 文件存储
文件存储是云中的文件存储,任何应用程序或设备都可以使用行业标准协议连接。你可以将文件写入文件存储,然后将其挂载为 PC 或 Mac 上的驱动器。
✅ 做一些研究:阅读 [Azure 文件存储](https://docs.microsoft.com/azure/storage/files/storage-files-introduction?WT.mc_id=academic-17441-jabenn)
## 将无服务器代码连接到存储
你的 Functions 应用现在需要连接到 Blob 存储,以存储来自 IoT Hub 的消息。有两种方法可以实现:
* 在函数代码中,使用 Blob 存储的 Python SDK 连接到 Blob 存储并将数据写入 Blob。
* 使用输出函数绑定,将函数的返回值绑定到 Blob 存储,并自动保存 Blob。
在本课中,你将使用 Python SDK 来了解如何与 Blob 存储交互。
![将 GPS 遥测数据从 IoT 设备发送到 IoT Hub再通过事件触发器发送到 Azure Functions最后保存到 Blob 存储](../../../../../translated_images/zh-CN/save-telemetry-to-storage-from-functions.ed3b1820980097f1.webp)
数据将以以下格式保存为 JSON Blob
```json
{
"device_id": <device_id>,
"timestamp" : <time>,
"gps" :
{
"lat" : <latitude>,
"lon" : <longitude>
}
}
```
### 任务 - 将无服务器代码连接到存储
1. 创建一个 Azure 存储账户。命名为类似 `gps<你的名字>` 的名称。
> ⚠️ 如果需要,可以参考[项目 2第 5 课中创建存储账户的说明](../../../2-farm/lessons/5-migrate-application-to-the-cloud/README.md#task---create-the-cloud-resources)。
如果你在之前的项目中已经创建了存储账户,可以重复使用。
> 💁 你可以在本课稍后将 Azure Functions 应用部署到同一个存储账户。
1. 运行以下命令以获取存储账户的连接字符串:
```sh
az storage account show-connection-string --output table \
--name <storage_name>
```
`<storage_name>` 替换为你在上一步中创建的存储账户名称。
1. 在 `local.settings.json` 文件中为存储账户连接字符串添加一个新条目,使用上一步的值。命名为 `STORAGE_CONNECTION_STRING`
1. 在 `requirements.txt` 文件中添加以下内容以安装 Azure 存储的 Pip 包:
```sh
azure-storage-blob
```
在你的虚拟环境中从该文件安装这些包。
> 如果遇到错误,请在虚拟环境中运行以下命令将 Pip 版本升级到最新版本,然后重试:
>
> ```sh
> pip install --upgrade pip
> ```
1. 在 `iot-hub-trigger``__init__.py` 文件中,添加以下导入语句:
```python
import json
import os
import uuid
from azure.storage.blob import BlobServiceClient, PublicAccess
```
`json` 系统模块将用于读取和写入 JSON`os` 系统模块将用于读取连接字符串,`uuid` 系统模块将用于为 GPS 读取生成唯一 ID。
`azure.storage.blob` 包包含用于操作 Blob 存储的 Python SDK。
1. 在 `main` 方法之前,添加以下辅助函数:
```python
def get_or_create_container(name):
connection_str = os.environ['STORAGE_CONNECTION_STRING']
blob_service_client = BlobServiceClient.from_connection_string(connection_str)
for container in blob_service_client.list_containers():
if container.name == name:
return blob_service_client.get_container_client(container.name)
return blob_service_client.create_container(name, public_access=PublicAccess.Container)
```
Python Blob SDK 没有辅助方法来在容器不存在时创建容器。此代码将从 `local.settings.json` 文件(或部署到云后从应用程序设置)加载连接字符串,然后从中创建一个 `BlobServiceClient` 类以与 Blob 存储账户交互。接着,它会遍历 Blob 存储账户的所有容器,查找具有提供名称的容器——如果找到,将返回一个可以与容器交互以创建 Blob 的 `ContainerClient` 类。如果未找到,则创建容器并返回新容器的客户端。
创建新容器时,将授予查询容器中 Blob 的公共访问权限。在下一课中,这将用于在地图上可视化 GPS 数据。
1. 与土壤湿度不同,此代码需要存储每个事件,因此在 `main` 函数的 `for event in events:` 循环中,在 `logging` 语句下方添加以下代码:
```python
device_id = event.iothub_metadata['connection-device-id']
blob_name = f'{device_id}/{str(uuid.uuid1())}.json'
```
此代码从事件元数据中获取设备 ID然后使用它创建一个 Blob 名称。Blob 可以存储在文件夹中,设备 ID 将用作文件夹名称,因此每个设备的所有 GPS 事件都存储在一个文件夹中。Blob 名称是该文件夹后跟文档名称,用正斜杠分隔,类似于 Linux 和 macOS 的路径(也类似于 Windows但 Windows 使用反斜杠)。文档名称是使用 Python `uuid` 模块生成的唯一 ID文件类型为 `json`
例如,对于设备 ID 为 `gps-sensor`Blob 名称可能是 `gps-sensor/a9487ac2-b9cf-11eb-b5cd-1e00621e3648.json`
1. 在此代码下方添加以下内容:
```python
container_client = get_or_create_container('gps-data')
blob = container_client.get_blob_client(blob_name)
```
此代码使用 `get_or_create_container` 辅助类获取容器客户端,然后使用 Blob 名称获取一个 Blob 客户端对象。这些 Blob 客户端可以引用现有 Blob或者像此处一样引用新 Blob。
1. 在此代码之后添加以下内容:
```python
event_body = json.loads(event.get_body().decode('utf-8'))
blob_body = {
'device_id' : device_id,
'timestamp' : event.iothub_metadata['enqueuedtime'],
'gps': event_body['gps']
}
```
这将构建将写入 Blob 存储的 Blob 的主体。它是一个包含设备 ID、消息发送到 IoT Hub 的时间以及遥测中的 GPS 坐标的 JSON 文档。
> 💁 使用消息的入队时间而不是当前时间来获取消息发送的时间非常重要。如果 Functions 应用未运行,消息可能会在 Hub 上停留一段时间。
1. 在此代码下方添加以下内容:
```python
logging.info(f'Writing blob to {blob_name} - {blob_body}')
blob.upload_blob(json.dumps(blob_body).encode('utf-8'))
```
此代码记录即将写入的 Blob 及其详细信息,然后将 Blob 主体上传为新 Blob 的内容。
1. 运行 Functions 应用。你将在输出中看到为所有 GPS 事件写入的 Blob
```output
[2021-05-21T01:31:14.325Z] Python EventHub trigger processed an event: {"gps": {"lat": 47.73092, "lon": -122.26206}}
...
[2021-05-21T01:31:14.351Z] Writing blob to gps-sensor/4b6089fe-ba8d-11eb-bc7b-1e00621e3648.json - {'device_id': 'gps-sensor', 'timestamp': '2021-05-21T00:57:53.878Z', 'gps': {'lat': 47.73092, 'lon': -122.26206}}
```
> 💁 确保你没有同时运行 IoT Hub 事件监视器。
> 💁 你可以在 [code/functions](../../../../../3-transport/lessons/2-store-location-data/code/functions) 文件夹中找到此代码。
### 任务 - 验证上传的 Blob
1. 要查看创建的 Blob你可以使用 [Azure Storage Explorer](https://azure.microsoft.com/features/storage-explorer/?WT.mc_id=academic-17441-jabenn)(一个免费的工具,用于查看和管理存储账户)或通过 CLI。
1. 要使用 CLI首先需要一个账户密钥。运行以下命令以获取此密钥
```sh
az storage account keys list --output table \
--account-name <storage_name>
```
`<storage_name>` 替换为存储账户的名称。
复制 `key1` 的值。
1. 运行以下命令列出容器中的 Blob
```sh
az storage blob list --container-name gps-data \
--output table \
--account-name <storage_name> \
--account-key <key1>
```
`<storage_name>` 替换为存储账户的名称,将 `<key1>` 替换为上一步中复制的 `key1` 值。
这将列出容器中的所有 Blob
```output
Name Blob Type Blob Tier Length Content Type Last Modified Snapshot
---------------------------------------------------- ----------- ----------- -------- ------------------------ ------------------------- ----------
gps-sensor/1810d55e-b9cf-11eb-9f5b-1e00621e3648.json BlockBlob Hot 45 application/octet-stream 2021-05-21T00:54:27+00:00
gps-sensor/18293e46-b9cf-11eb-9f5b-1e00621e3648.json BlockBlob Hot 45 application/octet-stream 2021-05-21T00:54:28+00:00
gps-sensor/1844549c-b9cf-11eb-9f5b-1e00621e3648.json BlockBlob Hot 45 application/octet-stream 2021-05-21T00:54:28+00:00
gps-sensor/1894d714-b9cf-11eb-9f5b-1e00621e3648.json BlockBlob Hot 45 application/octet-stream 2021-05-21T00:54:28+00:00
```
1. 使用以下命令下载其中一个 Blob
```sh
az storage blob download --container-name gps-data \
--account-name <storage_name> \
--account-key <key1> \
--name <blob_name> \
--file <file_name>
```
`<storage_name>` 替换为存储账户的名称,将 `<key1>` 替换为之前步骤中复制的 `key1` 值。
`<blob_name>` 替换为上一步输出中 `Name` 列的完整名称,包括文件夹名称。将 `<file_name>` 替换为保存 Blob 的本地文件名。
下载后,你可以在 VS Code 中打开 JSON 文件,查看包含 GPS 位置详细信息的 Blob
```json
{"device_id": "gps-sensor", "timestamp": "2021-05-21T00:57:53.878Z", "gps": {"lat": 47.73092, "lon": -122.26206}}
```
### 任务 - 将 Functions 应用部署到云端
现在你的 Functions 应用已经可以正常工作,可以将其部署到云端。
1. 创建一个新的 Azure Functions 应用,使用你之前创建的存储账户。命名为类似 `gps-sensor-` 的名称,并在末尾添加一个唯一标识符,例如一些随机单词或你的名字。
> ⚠️ 如果需要,可以参考[项目 2第 5 课中创建 Functions 应用的说明](../../../2-farm/lessons/5-migrate-application-to-the-cloud/README.md#task---create-the-cloud-resources)。
1. 将 `IOT_HUB_CONNECTION_STRING``STORAGE_CONNECTION_STRING` 值上传到应用程序设置。
> ⚠️ 如果需要,可以参考[项目 2第 5 课中上传应用程序设置的说明](../../../2-farm/lessons/5-migrate-application-to-the-cloud/README.md#task---upload-your-application-settings)。
1. 将本地 Functions 应用部署到云端。
> ⚠️ 如果需要,可以参考[项目2第5课中关于部署Functions应用的说明](../../../2-farm/lessons/5-migrate-application-to-the-cloud/README.md#task---deploy-your-functions-app-to-the-cloud)。
## 🚀 挑战
GPS 数据并非完全准确,检测到的位置可能会有几米的误差,尤其是在隧道和高楼密集的区域。
想一想卫星导航系统如何克服这个问题?你的导航系统拥有哪些数据可以用来更好地预测你的当前位置?
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/24)
## 复习与自学
* 阅读 [维基百科数据模型页面](https://wikipedia.org/wiki/Data_model) 了解结构化数据
* 阅读 [维基百科半结构化数据页面](https://wikipedia.org/wiki/Semi-structured_data) 了解半结构化数据
* 阅读 [维基百科非结构化数据页面](https://wikipedia.org/wiki/Unstructured_data) 了解非结构化数据
* 在 [Azure 存储文档](https://docs.microsoft.com/azure/storage/?WT.mc_id=academic-17441-jabenn) 中了解更多关于 Azure 存储及其不同存储类型的信息
## 作业
[研究函数绑定](assignment.md)
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。虽然我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于重要信息,建议使用专业人工翻译。我们对因使用此翻译而产生的任何误解或误读不承担责任。

@ -0,0 +1,22 @@
# 调查函数绑定
## 说明
函数绑定允许您的代码通过从 `main` 函数返回数据块,将其保存到 Blob 存储中。Azure 存储账户、集合以及其他详细信息在 `function.json` 文件中进行配置。
在使用 Azure 或其他 Microsoft 技术时,最好的信息来源是 [Microsoft 文档](https://docs.microsoft.com/?WT.mc_id=academic-17441-jabenn)。在本次任务中,您需要阅读 Azure Functions 绑定文档,以了解如何设置输出绑定。
以下是一些可以帮助您入门的页面:
* [Azure Functions 触发器和绑定概念](https://docs.microsoft.com/azure/azure-functions/functions-triggers-bindings?WT.mc_id=academic-17441-jabenn&tabs=python)
* [Azure Functions 的 Azure Blob 存储绑定概述](https://docs.microsoft.com/azure/azure-functions/functions-bindings-storage-blob?WT.mc_id=academic-17441-jabenn)
* [Azure Functions 的 Azure Blob 存储输出绑定](https://docs.microsoft.com/azure/azure-functions/functions-bindings-storage-blob-output?WT.mc_id=academic-17441-jabenn&tabs=python)
## 评分标准
| 标准 | 卓越 | 合格 | 需要改进 |
| -------- | --------- | -------- | ----------------- |
| 配置 Blob 存储输出绑定 | 能够成功配置输出绑定,返回数据块并将其成功存储到 Blob 存储中 | 能够配置输出绑定或返回数据块,但未能成功将其存储到 Blob 存储中 | 无法配置输出绑定 |
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们不对因使用此翻译而产生的任何误解或误读承担责任。

@ -0,0 +1,348 @@
# 可视化位置数据
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-13.a259db1485021be7d7c72e90842fbe0ab977529e8684c179b5fb1ea75e92b3ef.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看更大版本。
这段视频概述了 Azure Maps 与 IoT 的功能,这是本课将要讲解的服务。
[![Azure Maps - Microsoft Azure 企业级位置平台](https://img.youtube.com/vi/P5i2GFTtb2s/0.jpg)](https://www.youtube.com/watch?v=P5i2GFTtb2s)
> 🎥 点击上方图片观看视频
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/25)
## 简介
在上一课中,你学习了如何从传感器获取 GPS 数据并使用无服务器代码将其保存到云端的存储容器中。现在,你将学习如何在 Azure 地图上可视化这些点。你将学习如何在网页上创建地图,了解 GeoJSON 数据格式,并使用它在地图上绘制所有捕获的 GPS 点。
本课内容包括:
* [什么是数据可视化](../../../../../3-transport/lessons/3-visualize-location-data)
* [地图服务](../../../../../3-transport/lessons/3-visualize-location-data)
* [创建 Azure Maps 资源](../../../../../3-transport/lessons/3-visualize-location-data)
* [在网页上显示地图](../../../../../3-transport/lessons/3-visualize-location-data)
* [GeoJSON 格式](../../../../../3-transport/lessons/3-visualize-location-data)
* [使用 GeoJSON 在地图上绘制 GPS 数据](../../../../../3-transport/lessons/3-visualize-location-data)
> 💁 本课将涉及少量 HTML 和 JavaScript。如果你想了解更多关于使用 HTML 和 JavaScript 进行网页开发的内容,请查看 [Web development for beginners](https://github.com/microsoft/Web-Dev-For-Beginners)。
## 什么是数据可视化
数据可视化,顾名思义,是通过视觉化的方式展示数据,使人类更容易理解。它通常与图表和图形相关,但实际上是任何以图像形式表示数据的方法,帮助人类更好地理解数据并做出决策。
举个简单的例子——在农场项目中你捕获了土壤湿度数据。2021 年 6 月 1 日每小时捕获的土壤湿度数据表可能如下:
| 日期 | 读数 |
| ---------------- | ------: |
| 01/06/2021 00:00 | 257 |
| 01/06/2021 01:00 | 268 |
| 01/06/2021 02:00 | 295 |
| 01/06/2021 03:00 | 305 |
| 01/06/2021 04:00 | 325 |
| 01/06/2021 05:00 | 359 |
| 01/06/2021 06:00 | 398 |
| 01/06/2021 07:00 | 410 |
| 01/06/2021 08:00 | 429 |
| 01/06/2021 09:00 | 451 |
| 01/06/2021 10:00 | 460 |
| 01/06/2021 11:00 | 452 |
| 01/06/2021 12:00 | 420 |
| 01/06/2021 13:00 | 408 |
| 01/06/2021 14:00 | 431 |
| 01/06/2021 15:00 | 462 |
| 01/06/2021 16:00 | 432 |
| 01/06/2021 17:00 | 402 |
| 01/06/2021 18:00 | 387 |
| 01/06/2021 19:00 | 360 |
| 01/06/2021 20:00 | 358 |
| 01/06/2021 21:00 | 354 |
| 01/06/2021 22:00 | 356 |
| 01/06/2021 23:00 | 362 |
对于人类来说,理解这些数据可能很困难。这是一堆没有意义的数字。作为可视化这些数据的第一步,可以将其绘制成折线图:
![上述数据的折线图](../../../../../translated_images/zh-CN/chart-soil-moisture.fd6d9d0cdc0b5f75e78038ecb8945dfc84b38851359de99d84b16e3336d6d7c2.png)
进一步优化,可以添加一条线,表示当土壤湿度读数达到 450 时自动浇水系统启动的时间点:
![带有 450 线的土壤湿度折线图](../../../../../translated_images/zh-CN/chart-soil-moisture-relay.fbb391236d34a64d0abf1df396e9197e0a24df14150620b9cc820a64a55c9326.png)
这个图表可以快速显示土壤湿度水平以及浇水系统启动的时间点。
图表并不是唯一的可视化工具。追踪天气的 IoT 设备可以通过网页应用或移动应用使用符号来可视化天气状况,例如用云朵符号表示阴天,用雨云符号表示雨天等等。可视化数据的方法有很多种,有些严肃,有些有趣。
✅ 想一想你见过的数据可视化方式。哪些方式最清晰,能让你最快做出决策?
最好的可视化方式能让人类快速做出决策。例如,显示工业机器各种读数的仪表墙很难处理,但当某些东西出问题时,一个闪烁的红灯可以让人快速做出决策。有时候,最好的可视化方式就是一个闪烁的灯!
在处理 GPS 数据时,最清晰的可视化方式是将数据绘制在地图上。例如,显示送货卡车的地图可以帮助加工厂的工作人员了解卡车的到达时间。如果这张地图不仅显示卡车当前位置,还显示卡车的货物信息,那么加工厂的工作人员可以提前计划——如果他们看到一辆冷藏卡车接近,他们就知道需要准备冷藏空间。
## 地图服务
处理地图是一个有趣的练习,有许多地图服务可供选择,例如 Bing Maps、Leaflet、Open Street Maps 和 Google Maps。在本课中你将学习 [Azure Maps](https://azure.microsoft.com/services/azure-maps/?WT.mc_id=academic-17441-jabenn) 以及如何使用它来显示你的 GPS 数据。
![Azure Maps 标志](../../../../../translated_images/zh-CN/azure-maps-logo.35d01dcfbd81fe6140e94257aaa1538f785a58c91576d14e0ebe7a2f6c694b99.png)
Azure Maps 是“一组地理空间服务和 SDK使用最新的地图数据为网页和移动应用提供地理背景。”开发者可以使用这些工具创建美观、交互式的地图这些地图可以提供推荐的交通路线、交通事故信息、室内导航、搜索功能、海拔信息、天气服务等。
✅ 试试一些 [地图代码示例](https://docs.microsoft.com/samples/browse?WT.mc_id=academic-17441-jabenn&products=azure-maps)
你可以将地图显示为空白画布、瓦片、卫星图像、带有道路叠加的卫星图像、各种类型的灰度地图、带有阴影的地图以显示海拔、夜视地图以及高对比度地图。通过与 [Azure Event Grid](https://azure.microsoft.com/services/event-grid/?WT.mc_id=academic-17441-jabenn) 集成,你可以在地图上获得实时更新。你可以通过启用各种控件来控制地图的行为和外观,使地图对事件如缩放、拖动和点击做出反应。为了控制地图的外观,你可以添加包括气泡、线条、多边形、热图等的图层。选择哪种地图样式取决于你选择的 SDK。
你可以通过以下方式访问 Azure Maps API[REST API](https://docs.microsoft.com/javascript/api/azure-maps-rest/?WT.mc_id=academic-17441-jabenn&view=azure-maps-typescript-latest)、[Web SDK](https://docs.microsoft.com/azure/azure-maps/how-to-use-map-control?WT.mc_id=academic-17441-jabenn) 或者如果你正在构建移动应用,可以使用 [Android SDK](https://docs.microsoft.com/azure/azure-maps/how-to-use-android-map-control-library?WT.mc_id=academic-17441-jabenn&pivots=programming-language-java-android)。
在本课中,你将使用 Web SDK 绘制地图并显示传感器的 GPS 位置路径。
## 创建 Azure Maps 资源
第一步是创建一个 Azure Maps 账户。
### 任务 - 创建 Azure Maps 资源
1. 在终端或命令提示符中运行以下命令,在你的 `gps-sensor` 资源组中创建一个 Azure Maps 资源:
```sh
az maps account create --name gps-sensor \
--resource-group gps-sensor \
--accept-tos \
--sku S1
```
这将创建一个名为 `gps-sensor` 的 Azure Maps 资源。使用的层级是 `S1`,这是一个付费层级,包含一系列功能,但提供了大量免费的调用。
> 💁 要查看使用 Azure Maps 的费用,请访问 [Azure Maps 定价页面](https://azure.microsoft.com/pricing/details/azure-maps/?WT.mc_id=academic-17441-jabenn)。
1. 你需要一个地图资源的 API 密钥。使用以下命令获取该密钥:
```sh
az maps account keys list --name gps-sensor \
--resource-group gps-sensor \
--output table
```
复制 `PrimaryKey` 的值。
## 在网页上显示地图
接下来,你可以在网页上显示地图。我们将使用一个 `html` 文件来构建一个小型网页应用;请注意,在生产或团队环境中,你的网页应用可能会有更多的组件。
### 任务 - 在网页上显示地图
1. 在本地计算机的某个文件夹中创建一个名为 index.html 的文件。添加 HTML 标记以容纳地图:
```html
<html>
<head>
<style>
#myMap {
width:100%;
height:100%;
}
</style>
</head>
<body onload="init()">
<div id="myMap"></div>
</body>
</html>
```
地图将加载到 `myMap``div` 中。一些样式允许它占据页面的宽度和高度。
> 🎓 `div` 是网页中的一个部分,可以命名和设置样式。
1. 在打开的 `<head>` 标签下,添加一个外部样式表来控制地图显示,以及一个来自 Web SDK 的外部脚本来管理地图行为:
```html
<link rel="stylesheet" href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" type="text/css" />
<script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js"></script>
```
这个样式表包含地图外观的设置,而脚本文件包含加载地图的代码。添加这些代码类似于包含 C++ 头文件或导入 Python 模块。
1. 在该脚本下方,添加一个脚本块以启动地图。
```javascript
<script type='text/javascript'>
function init() {
var map = new atlas.Map('myMap', {
center: [-122.26473, 47.73444],
zoom: 12,
authOptions: {
authType: "subscriptionKey",
subscriptionKey: "<subscription_key>",
}
});
}
</script>
```
`<subscription_key>` 替换为你的 Azure Maps 账户的 API 密钥。
如果你在浏览器中打开你的 `index.html` 页面,你应该会看到一个地图加载并聚焦在西雅图地区。
![显示西雅图的地图,西雅图是美国华盛顿州的一个城市](../../../../../translated_images/zh-CN/map-image.8fb2c53eb23ef39c1c0a4410a5282e879b3b452b707eb066ff04c5488d3d72b7.png)
✅ 尝试调整缩放和中心参数以更改地图显示。你可以添加与你数据的纬度和经度对应的不同坐标来重新定位地图。
> 💁 在本地运行网页应用的更好方法是安装 [http-server](https://www.npmjs.com/package/http-server)。你需要先安装 [node.js](https://nodejs.org/) 和 [npm](https://www.npmjs.com/)。安装这些工具后,你可以导航到 `index.html` 文件所在的位置并输入 `http-server`。网页应用将会在本地服务器上打开 [http://127.0.0.1:8080/](http://127.0.0.1:8080/)。
## GeoJSON 格式
现在你已经设置了网页应用并显示了地图,接下来需要从存储账户中提取 GPS 数据,并在地图上添加标记层显示这些数据。在此之前,我们先来了解 Azure Maps 所需的 [GeoJSON](https://wikipedia.org/wiki/GeoJSON) 格式。
[GeoJSON](https://geojson.org/) 是一种开放标准的 JSON 规范,具有专门的格式设计,用于处理地理相关数据。你可以通过测试示例数据来了解它,例如使用 [geojson.io](https://geojson.io),这也是一个调试 GeoJSON 文件的有用工具。
示例 GeoJSON 数据如下所示:
```json
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-2.10237979888916,
57.164918677004714
]
}
}
]
}
```
特别需要注意的是数据如何嵌套为 `Feature`,并包含在 `FeatureCollection` 中。在该对象中可以找到 `geometry`,其中 `coordinates` 指示纬度和经度。
✅ 在构建你的 GeoJSON 时,请注意对象中 `latitude``longitude` 的顺序否则你的点可能不会出现在正确的位置GeoJSON 期望点数据的顺序为 `lon,lat`,而不是 `lat,lon`
`Geometry` 可以有不同的类型,例如单点或多边形。在这个例子中,它是一个点,指定了两个坐标:经度和纬度。
✅ Azure Maps 支持标准 GeoJSON并提供一些[增强功能](https://docs.microsoft.com/azure/azure-maps/extend-geojson?WT.mc_id=academic-17441-jabenn),包括绘制圆形和其他几何图形的能力。
## 使用 GeoJSON 在地图上绘制 GPS 数据
现在,您已经准备好使用上一课中构建的存储中的数据。提醒一下,这些数据存储为 Blob 存储中的多个文件,因此您需要检索这些文件并解析它们,以便 Azure Maps 可以使用这些数据。
### 任务 - 配置存储以便从网页访问
如果您调用存储以获取数据,可能会惊讶地发现浏览器控制台中出现错误。这是因为您需要为此存储设置 [CORS](https://developer.mozilla.org/docs/Web/HTTP/CORS) 权限,以允许外部网页应用读取其数据。
> 🎓 CORS 代表“跨域资源共享”,通常需要在 Azure 中显式设置以确保安全性。它可以阻止您不期望的网站访问您的数据。
1. 运行以下命令以启用 CORS
```sh
az storage cors add --methods GET \
--origins "*" \
--services b \
--account-name <storage_name> \
--account-key <key1>
```
`<storage_name>` 替换为您的存储账户名称。将 `<key1>` 替换为您的存储账户的账户密钥。
此命令允许任何网站(通配符 `*` 表示任何网站)向您的存储账户发出 *GET* 请求,即获取数据。`--services b` 表示仅对 Blob 应用此设置。
### 任务 - 从存储加载 GPS 数据
1. 将 `init` 函数的全部内容替换为以下代码:
```javascript
fetch("https://<storage_name>.blob.core.windows.net/gps-data/?restype=container&comp=list")
.then(response => response.text())
.then(str => new window.DOMParser().parseFromString(str, "text/xml"))
.then(xml => {
let blobList = Array.from(xml.querySelectorAll("Url"));
blobList.forEach(async blobUrl => {
loadJSON(blobUrl.innerHTML)
});
})
.then( response => {
map = new atlas.Map('myMap', {
center: [-122.26473, 47.73444],
zoom: 14,
authOptions: {
authType: "subscriptionKey",
subscriptionKey: "<subscription_key>",
}
});
map.events.add('ready', function () {
var source = new atlas.source.DataSource();
map.sources.add(source);
map.layers.add(new atlas.layer.BubbleLayer(source));
source.add(features);
})
})
```
`<storage_name>` 替换为您的存储账户名称。将 `<subscription_key>` 替换为您的 Azure Maps 账户的 API 密钥。
这里发生了几件事。首先,代码通过使用您的存储账户名称构建的 URL 端点从 Blob 容器中获取您的 GPS 数据。此 URL 从 `gps-data` 中检索,表明资源类型是容器(`restype=container`),并列出所有 Blob 的信息。此列表不会返回 Blob 本身,但会返回每个 Blob 的 URL可用于加载 Blob 数据。
> 💁 您可以将此 URL 放入浏览器中查看容器中所有 Blob 的详细信息。每个项目都会有一个 `Url` 属性,您也可以在浏览器中加载以查看 Blob 的内容。
然后,此代码加载每个 Blob调用一个 `loadJSON` 函数(将在下一步中创建)。接着,它创建地图控件,并向 `ready` 事件添加代码。此事件在地图显示在网页上时被调用。
`ready` 事件创建了一个 Azure Maps 数据源——一个包含 GeoJSON 数据的容器,稍后将填充此数据源。然后使用此数据源创建一个气泡图层——即地图上的一组圆形,中心点位于 GeoJSON 中的每个点。
1. 在 `init` 函数下方的脚本块中添加 `loadJSON` 函数:
```javascript
var map, features;
function loadJSON(file) {
var xhr = new XMLHttpRequest();
features = [];
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
gps = JSON.parse(xhr.responseText)
features.push(
new atlas.data.Feature(new atlas.data.Point([parseFloat(gps.gps.lon), parseFloat(gps.gps.lat)]))
)
}
}
};
xhr.open("GET", file, true);
xhr.send();
}
```
此函数由 fetch 例程调用,用于解析 JSON 数据并将其转换为经度和纬度坐标作为 GeoJSON 读取。
解析完成后,数据被设置为 GeoJSON 的一个 `Feature`。地图将被初始化,并且小气泡会出现在数据路径周围:
1. 在浏览器中加载 HTML 页面。页面将加载地图,然后从存储中加载所有 GPS 数据并将其绘制在地图上。
![西雅图附近圣爱德华州立公园的地图,显示公园边缘路径上的圆形标记](../../../../../translated_images/zh-CN/map-path.896832e72dc696ffe20650e4051027d4855442d955f93fdbb80bb417ca8a406f.png)
> 💁 您可以在 [code](../../../../../3-transport/lessons/3-visualize-location-data/code) 文件夹中找到此代码。
---
## 🚀 挑战
在地图上显示静态数据作为标记是很不错的。您能否增强此网页应用,添加动画并显示标记随时间变化的路径,使用带时间戳的 JSON 文件?以下是一些[使用地图动画的示例](https://azuremapscodesamples.azurewebsites.net/)。
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/26)
## 复习与自学
Azure Maps 在处理 IoT 设备时特别有用。
* 在 [Microsoft Docs 上的 Azure Maps 文档](https://docs.microsoft.com/azure/azure-maps/tutorial-iot-hub-maps?WT.mc_id=academic-17441-jabenn)中研究一些使用案例。
* 通过 [Microsoft Learn 上的 Azure Maps 自学模块](https://docs.microsoft.com/learn/modules/create-your-first-app-with-azure-maps/?WT.mc_id=academic-17441-jabenn)加深您对地图制作和航点的了解。
## 作业
[部署您的应用](assignment.md)
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。对于因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,14 @@
# 部署你的应用
## 说明
有多种方法可以部署你的应用并与世界分享,包括使用 GitHub Pages 或众多服务提供商中的一个。一个非常优秀的选择是使用 Azure Static Web Apps。在这个任务中按照[这些说明](https://github.com/Azure/static-web-apps-cli)或观看[这些视频](https://www.youtube.com/watch?v=ADVGIXciYn8&list=PLlrxD0HtieHgMPeBaDQFx9yNuFxx6S1VG&index=3),构建你的网页应用并将其部署到云端。使用 Azure Static Web Apps 的一个好处是你可以在门户中隐藏任何 API 密钥,因此可以借此机会将你的 subscriptionKey 重构为一个变量并存储在云端。
## 评分标准
| 标准 | 卓越表现 | 合格表现 | 需要改进 |
| -------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| | 一个功能正常的网页应用被展示在一个有文档的 GitHub 仓库中,并且其 subscriptionKey 存储在云端并通过变量调用 | 一个功能正常的网页应用被展示在一个有文档的 GitHub 仓库中,但其 subscriptionKey 未存储在云端 | 网页应用存在错误或无法正常工作 |
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,477 @@
# 地理围栏
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-14.63980c5150ae3c153e770fb71d044c1845dce79248d86bed9fc525adf3ede73c.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看大图。
这段视频概述了地理围栏以及如何在 Azure Maps 中使用它们,这是本课将要讲解的主题:
[![微软开发者物联网节目中的 Azure Maps 地理围栏](https://img.youtube.com/vi/nsrgYhaYNVY/0.jpg)](https://www.youtube.com/watch?v=nsrgYhaYNVY)
> 🎥 点击上方图片观看视频
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/27)
## 简介
在前面三节课中,你已经使用物联网技术定位了从农场运送农产品到加工中心的卡车。你捕获了 GPS 数据,将其发送到云端存储,并在地图上进行了可视化。接下来的步骤是提高供应链效率:当卡车即将到达加工中心时收到警报,以便卸货所需的工作人员可以提前准备好叉车和其他设备。一旦车辆到达,他们可以快速卸货,从而避免支付卡车和司机的等待费用。
在本课中,你将学习地理围栏——定义的地理空间区域,例如距离加工中心 2 公里车程范围内的区域,以及如何测试 GPS 坐标是否在地理围栏内或外,从而判断 GPS 传感器是否到达或离开某个区域。
本课将涵盖以下内容:
* [什么是地理围栏](../../../../../3-transport/lessons/4-geofences)
* [定义地理围栏](../../../../../3-transport/lessons/4-geofences)
* [测试点是否在地理围栏内](../../../../../3-transport/lessons/4-geofences)
* [在无服务器代码中使用地理围栏](../../../../../3-transport/lessons/4-geofences)
> 🗑 这是本项目的最后一课,因此在完成本课和作业后,不要忘记清理你的云服务。你需要这些服务来完成作业,所以请确保先完成作业。
>
> 如果需要,请参考[清理项目指南](../../../clean-up.md)获取相关说明。
## 什么是地理围栏
地理围栏是一个虚拟的边界,用于定义现实世界中的地理区域。地理围栏可以是以点和半径定义的圆形(例如围绕某建筑物 100 米范围的圆),也可以是覆盖某个区域的多边形,例如学校区域、城市边界或大学/办公园区。
![一些地理围栏示例,显示微软公司商店周围的圆形地理围栏,以及微软西区园区的多边形地理围栏](../../../../../translated_images/zh-CN/geofence-examples.172fbc534665769f6e1a1ddcf75e3b25183cd10354c80cc603ba44b635390e1a.png)
> 💁 你可能已经在不知不觉中使用过地理围栏。如果你曾使用 iOS 提醒事项应用或 Google Keep 设置基于位置的提醒,那么你就使用过地理围栏。这些应用会根据提供的位置设置地理围栏,并在你的手机进入地理围栏时提醒你。
以下是一些需要知道车辆是否在地理围栏内或外的原因:
* **卸货准备** - 收到车辆到达现场的通知可以让工作人员提前准备卸货,从而减少车辆等待时间。这可以让司机在一天内完成更多的交付,减少等待时间。
* **税务合规** - 一些国家(如新西兰)对柴油车辆在公共道路上行驶的里程征收道路税,税额基于车辆重量。使用地理围栏可以区分车辆在公共道路和私人道路(如农场或伐木区)上的行驶里程。
* **防盗监控** - 如果车辆应该只停留在某个区域(如农场),但离开了地理围栏,则可能被盗。
* **位置合规** - 工作场所、农场或工厂的某些区域可能禁止特定车辆进入,例如防止携带人工肥料和农药的车辆进入种植有机农产品的田地。如果进入地理围栏,则车辆不符合规定,司机可以收到通知。
✅ 你能想到其他地理围栏的用途吗?
Azure Maps你在上一课中用来可视化 GPS 数据的服务)允许你定义地理围栏,并测试某个点是否在地理围栏内或外。
## 定义地理围栏
地理围栏使用 GeoJSON 定义,与上一课中添加到地图上的点相同。不过,这次不是 `Point` 值的 `FeatureCollection`,而是包含 `Polygon``FeatureCollection`
```json
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-122.13393688201903,
47.63829579223815
],
[
-122.13389128446579,
47.63782047131512
],
[
-122.13240802288054,
47.63783312249837
],
[
-122.13238388299942,
47.63829037035086
],
[
-122.13393688201903,
47.63829579223815
]
]
]
},
"properties": {
"geometryId": "1"
}
}
]
}
```
多边形的每个点由一个经度和纬度对组成,并以数组形式定义,这些点的数组被设置为 `coordinates`。在上一课的 `Point` 中,`coordinates` 是一个包含两个值(纬度和经度)的数组,而在 `Polygon` 中,它是一个包含多个数组的数组,每个数组包含两个值(经度和纬度)。
> 💁 请记住GeoJSON 使用 `经度, 纬度` 的顺序,而不是 `纬度, 经度`
多边形坐标数组的条目数总是比多边形的点数多 1最后一个条目与第一个条目相同用于闭合多边形。例如对于一个矩形会有 5 个点。
![一个矩形及其坐标](../../../../../translated_images/zh-CN/polygon-points.302193da381cb415.webp)
在上图中,有一个矩形。多边形坐标从左上角的 47,-122 开始,然后向右移动到 47,-121再向下到 46,-121然后向左到 46,-122最后回到起点 47,-122。这样多边形就有 5 个点:左上角、右上角、右下角、左下角,以及闭合的左上角。
✅ 尝试创建一个围绕你家或学校的 GeoJSON 多边形。可以使用 [GeoJSON.io](https://geojson.io/) 这样的工具。
### 任务 - 定义地理围栏
要在 Azure Maps 中使用地理围栏,首先需要将其上传到你的 Azure Maps 账户。一旦上传,你将获得一个唯一的 ID用于测试点是否在地理围栏内。要将地理围栏上传到 Azure Maps你需要使用 Maps Web API。可以使用名为 [curl](https://curl.se) 的工具调用 Azure Maps Web API。
> 🎓 Curl 是一个命令行工具,用于对 Web 端点发起请求。
1. 如果你使用的是 Linux、macOS 或较新的 Windows 10 版本,你可能已经安装了 curl。运行以下命令检查
```sh
curl --version
```
如果没有看到 curl 的版本信息,你需要从 [curl 下载页面](https://curl.se/download.html) 安装它。
> 💁 如果你熟悉 Postman也可以选择使用它。
1. 创建一个包含多边形的 GeoJSON 文件。你将使用你的 GPS 传感器测试它,因此请围绕你当前的位置创建一个多边形。你可以手动编辑上面提供的 GeoJSON 示例,或者使用 [GeoJSON.io](https://geojson.io/) 这样的工具。
GeoJSON 需要包含一个 `FeatureCollection`,其中包含一个 `geometry` 类型为 `Polygon``Feature`
你**必须**在 `geometry` 元素同级添加一个 `properties` 元素,并在其中包含一个 `geometryId`
```json
"properties": {
"geometryId": "1"
}
```
如果你使用 [GeoJSON.io](https://geojson.io/),则需要手动将此项添加到空的 `properties` 元素中,可以在下载 JSON 文件后添加,也可以在应用的 JSON 编辑器中添加。
`geometryId` 在文件中必须是唯一的。你可以在同一个 GeoJSON 文件中的 `FeatureCollection` 中上传多个地理围栏,只要每个地理围栏都有不同的 `geometryId`。如果从不同的文件在不同时间上传,多边形可以有相同的 `geometryId`
1. 将此文件保存为 `geofence.json`,并在终端或控制台中导航到文件保存的位置。
1. 运行以下 curl 命令创建地理围栏:
```sh
curl --request POST 'https://atlas.microsoft.com/mapData/upload?api-version=1.0&dataFormat=geojson&subscription-key=<subscription_key>' \
--header 'Content-Type: application/json' \
--include \
--data @geofence.json
```
将 URL 中的 `<subscription_key>` 替换为你的 Azure Maps 账户的 API 密钥。
该 URL 使用 `https://atlas.microsoft.com/mapData/upload` API 上传地图数据。调用中包含一个 `api-version` 参数,用于指定使用的 Azure Maps API 版本,以便 API 随时间变化但保持向后兼容。上传的数据格式设置为 `geojson`
这将运行 POST 请求上传 API并返回一组响应头其中包括一个名为 `location` 的头:
```output
content-type: application/json
location: https://us.atlas.microsoft.com/mapData/operations/1560ced6-3a80-46f2-84b2-5b1531820eab?api-version=1.0
x-ms-azuremaps-region: West US 2
x-content-type-options: nosniff
strict-transport-security: max-age=31536000; includeSubDomains
x-cache: CONFIG_NOCACHE
date: Sat, 22 May 2021 21:34:57 GMT
content-length: 0
```
> 🎓 调用 Web 端点时,可以通过在 URL 后添加 `?` 和键值对(如 `key=value`)传递参数,多个键值对用 `&` 分隔。
1. Azure Maps 不会立即处理此请求,因此你需要检查上传请求是否完成。使用 `location` 头中给出的 URL 进行 GET 请求以查看状态。你需要在 `location` URL 的末尾添加你的订阅密钥,格式为 `&subscription-key=<subscription_key>`,将 `<subscription_key>` 替换为你的 Azure Maps 账户的 API 密钥。运行以下命令:
```sh
curl --request GET '<location>&subscription-key=<subscription_key>'
```
`<location>` 替换为 `location` 头的值,将 `<subscription_key>` 替换为你的 Azure Maps 账户的 API 密钥。
1. 检查响应中的 `status` 值。如果不是 `Succeeded`,请等待一分钟后重试。
1. 一旦状态返回为 `Succeeded`,查看响应中的 `resourceLocation`。其中包含 GeoJSON 对象的唯一 ID称为 UDID。UDID 是 `metadata/` 之后的值,不包括 `api-version`。例如,如果 `resourceLocation` 是:
```json
{
"resourceLocation": "https://us.atlas.microsoft.com/mapData/metadata/7c3776eb-da87-4c52-ae83-caadf980323a?api-version=1.0"
}
```
那么 UDID 将是 `7c3776eb-da87-4c52-ae83-caadf980323a`
保存此 UDID因为你需要它来测试地理围栏。
## 测试点是否在地理围栏内
一旦多边形上传到 Azure Maps你可以测试某个点是否在地理围栏内或外。通过 Web API 请求传递地理围栏的 UDID以及要测试点的纬度和经度即可。
在发起此请求时,你还可以传递一个名为 `searchBuffer` 的值。此值用于指定返回结果的精确度。原因是 GPS 并不完全准确,有时位置可能会偏差几米甚至更多。默认的搜索缓冲区为 50 米,但你可以设置 0 米到 500 米之间的值。
API 调用返回的结果中包含一个 `distance` 值,表示到地理围栏边缘最近点的距离。如果点在地理围栏外,则为正值;如果在地理围栏内,则为负值。如果此距离小于搜索缓冲区,则返回实际距离(以米为单位);否则,值为 999 或 -999。999 表示点在地理围栏外超过搜索缓冲区,-999 表示点在地理围栏内超过搜索缓冲区。
![一个地理围栏及其周围 50 米的搜索缓冲区](../../../../../translated_images/zh-CN/search-buffer-and-distance.e6a79af3898183c7.webp)
在上图中,地理围栏有一个 50 米的搜索缓冲区。
* 地理围栏中心的一个点,远在搜索缓冲区内,距离为 **-999**
* 搜索缓冲区外的一个点,距离为 **999**
* 地理围栏内且在搜索缓冲区内的一个点,距离地理围栏 6 米,距离为 **6m**
* 地理围栏外且在搜索缓冲区内的一个点,距离地理围栏 39 米,距离为 **39m**
了解到地理围栏边缘的距离,并结合其他信息(如其他 GPS 读数、速度和道路数据)在基于车辆位置做出决策时非常重要。
例如,假设 GPS 读数显示车辆沿着一条道路行驶,而这条道路最终靠近地理围栏。如果单个 GPS 值不准确,将车辆定位在地理围栏内,尽管没有车辆通行的入口,那么可以忽略该值。
![一条 GPS 轨迹显示车辆沿 520 公路经过微软园区GPS 读数沿着道路分布,除了一个点位于园区内的地理围栏中](../../../../../translated_images/zh-CN/geofence-crossing-inaccurate-gps.6a3ed911202ad9cabb66d3964888cec03a42c61d5b8f536ad5bdc99716b370f5.png)
在上图中微软园区的一部分被设置了地理围栏。红线表示一辆卡车沿着520公路行驶圆点表示GPS读数。大多数读数是准确的沿着520公路但有一个不准确的读数显示在地理围栏内。这个读数显然是错误的——卡车不可能突然从520公路转入园区然后又回到520公路。检查地理围栏的代码需要在处理地理围栏测试结果之前考虑之前的读数。
✅ 你需要检查哪些额外的数据来判断一个GPS读数是否可以被认为是正确的
### 任务 - 测试点是否在地理围栏内
1. 首先构建用于Web API查询的URL。格式如下
```output
https://atlas.microsoft.com/spatial/geofence/json?api-version=1.0&deviceId=gps-sensor&subscription-key=<subscription-key>&udid=<UDID>&lat=<lat>&lon=<lon>
```
`<subscription_key>` 替换为你的Azure Maps账户的API密钥。
`<UDID>` 替换为上一任务中地理围栏的UDID。
`<lat>``<lon>` 替换为你想测试的纬度和经度。
这个URL使用 `https://atlas.microsoft.com/spatial/geofence/json` API来查询使用GeoJSON定义的地理围栏。它针对 `1.0` API版本。`deviceId` 参数是必需的,应该是提供纬度和经度的设备名称。
默认搜索缓冲区是50米你可以通过传递额外的参数 `searchBuffer=<distance>` 来更改,将 `<distance>` 设置为以米为单位的搜索缓冲距离范围为0到500。
1. 使用curl对该URL发起GET请求
```sh
curl --request GET '<URL>'
```
> 💁 如果你收到响应代码 `BadRequest`,并伴有以下错误:
>
> ```output
> Invalid GeoJSON: All feature properties should contain a geometryId, which is used for identifying the geofence.
> ```
>
> 那么你的GeoJSON缺少 `properties` 部分中的 `geometryId`。你需要修复你的GeoJSON然后重复上述步骤重新上传并获取新的UDID。
1. 响应将包含一个 `geometries` 列表每个几何体对应于用于创建地理围栏的GeoJSON中定义的多边形。每个几何体有三个相关字段`distance`、`nearestLat` 和 `nearestLon`
```output
{
"geometries": [
{
"deviceId": "gps-sensor",
"udId": "7c3776eb-da87-4c52-ae83-caadf980323a",
"geometryId": "1",
"distance": 999.0,
"nearestLat": 47.645875,
"nearestLon": -122.142713
}
],
"expiredGeofenceGeometryId": [],
"invalidPeriodGeofenceGeometryId": []
}
```
* `nearestLat``nearestLon` 是地理围栏边缘上最接近测试位置的点的纬度和经度。
* `distance` 是测试位置到地理围栏边缘最近点的距离。负数表示在地理围栏内正数表示在地理围栏外。这个值将小于50默认搜索缓冲区或999。
1. 使用地理围栏内外的多个位置重复此操作。
## 从无服务器代码中使用地理围栏
现在你可以为你的Functions应用添加一个新的触发器用于测试IoT Hub GPS事件数据是否在地理围栏内。
### 消费组
正如你在之前的课程中记得的IoT Hub允许你重播已接收但未处理的事件。但如果多个触发器连接会发生什么它如何知道哪个触发器处理了哪些事件
答案是它无法知道!因此,你可以定义多个独立的连接来读取事件,每个连接可以管理未读消息的重播。这些被称为*消费组*。当你连接到端点时,可以指定你想连接的消费组。应用程序的每个组件将连接到不同的消费组。
![一个IoT Hub有3个消费组将相同的消息分发到3个不同的Functions应用](../../../../../translated_images/zh-CN/consumer-groups.a3262e26fc27ba2092863678ad57af15c7223416e388a23f330c058cf4358630.png)
理论上每个消费组最多可以连接5个应用程序它们都会在消息到达时接收消息。最佳实践是每个消费组只允许一个应用程序访问以避免重复处理消息并确保在重启时所有排队的消息都能正确处理。例如如果你在本地启动了Functions应用同时在云端运行它们都会处理消息导致存储账户中存储重复的blob。
如果你查看之前课程中创建的IoT Hub触发器的 `function.json` 文件,你会看到事件中心触发器绑定部分中的消费组:
```json
"consumerGroup": "$Default"
```
当你创建一个IoT Hub时默认会创建 `$Default` 消费组。如果你想添加额外的触发器,可以使用新的消费组。
> 💁 在本课程中你将使用一个不同的函数来测试地理围栏而不是存储GPS数据的函数。这是为了展示如何使用消费组并分离代码使其更易于阅读和理解。在生产应用中有许多方法可以架构这一点——将两者放在一个函数中使用存储账户上的触发器运行一个函数来检查地理围栏或使用多个函数。没有“正确的方法”这取决于你的应用程序的其他部分和需求。
### 任务 - 创建一个新的消费组
1. 运行以下命令为你的IoT Hub创建一个名为 `geofence` 的新消费组:
```sh
az iot hub consumer-group create --name geofence \
--hub-name <hub_name>
```
`<hub_name>` 替换为你为IoT Hub使用的名称。
1. 如果你想查看IoT Hub的所有消费组运行以下命令
```sh
az iot hub consumer-group list --output table \
--hub-name <hub_name>
```
`<hub_name>` 替换为你为IoT Hub使用的名称。这将列出所有消费组。
```output
Name ResourceGroup
-------- ---------------
$Default gps-sensor
geofence gps-sensor
```
> 💁 在之前的课程中运行IoT Hub事件监视器时它连接到了 `$Default` 消费组。这就是为什么你不能同时运行事件监视器和事件触发器。如果你想同时运行两者可以为所有Functions应用使用其他消费组并保留 `$Default` 用于事件监视器。
### 任务 - 创建一个新的IoT Hub触发器
1. 为你在之前课程中创建的 `gps-trigger` Functions应用添加一个新的IoT Hub事件触发器。将此函数命名为 `geofence-trigger`
> ⚠️ 如果需要,可以参考[项目2课程5中创建IoT Hub事件触发器的说明](../../../2-farm/lessons/5-migrate-application-to-the-cloud/README.md#create-an-iot-hub-event-trigger)。
1. 在 `function.json` 文件中配置IoT Hub连接字符串。`local.settings.json` 在Functions应用中的所有触发器之间共享。
1. 更新 `function.json` 文件中 `consumerGroup` 的值以引用新的 `geofence` 消费组:
```json
"consumerGroup": "geofence"
```
1. 你需要在此触发器中使用Azure Maps账户的订阅密钥因此在 `local.settings.json` 文件中添加一个名为 `MAPS_KEY` 的新条目。
1. 运行Functions应用以确保它正在连接并处理消息。之前课程中的 `iot-hub-trigger` 也会运行并将blob上传到存储。
> 为避免存储账户中出现重复的GPS读数你可以停止在云端运行的Functions应用。为此请使用以下命令
>
> ```sh
> az functionapp stop --resource-group gps-sensor \
> --name <functions_app_name>
> ```
>
> 将 `<functions_app_name>` 替换为你为Functions应用使用的名称。
>
> 稍后可以使用以下命令重新启动:
>
> ```sh
> az functionapp start --resource-group gps-sensor \
> --name <functions_app_name>
> ```
>
> 将 `<functions_app_name>` 替换为你为Functions应用使用的名称。
### 任务 - 从触发器测试地理围栏
在本课程的前面部分你使用curl查询了地理围栏以查看一个点是否位于内部或外部。你可以从触发器内部发起类似的Web请求。
1. 要查询地理围栏你需要它的UDID。在 `local.settings.json` 文件中添加一个名为 `GEOFENCE_UDID` 的新条目,并设置该值。
1. 打开新 `geofence-trigger` 触发器的 `__init__.py` 文件。
1. 在文件顶部添加以下导入:
```python
import json
import os
import requests
```
`requests` 包允许你发起Web API调用。Azure Maps没有Python SDK你需要通过Web API调用来从Python代码中使用它。
1. 在 `main` 方法的开头添加以下两行代码以获取Maps订阅密钥
```python
maps_key = os.environ['MAPS_KEY']
geofence_udid = os.environ['GEOFENCE_UDID']
```
1. 在 `for event in events` 循环中,添加以下代码以从每个事件中获取纬度和经度:
```python
event_body = json.loads(event.get_body().decode('utf-8'))
lat = event_body['gps']['lat']
lon = event_body['gps']['lon']
```
此代码将事件主体中的JSON转换为字典然后从 `gps` 字段中提取 `lat``lon`
1. 使用 `requests`与使用curl构建长URL不同你可以仅使用URL部分并将参数作为字典传递。添加以下代码以定义要调用的URL并配置参数
```python
url = 'https://atlas.microsoft.com/spatial/geofence/json'
params = {
'api-version': 1.0,
'deviceId': 'gps-sensor',
'subscription-key': maps_key,
'udid' : geofence_udid,
'lat' : lat,
'lon' : lon
}
```
`params` 字典中的项将与通过curl调用Web API时使用的键值对匹配。
1. 添加以下代码行以调用Web API
```python
response = requests.get(url, params=params)
response_body = json.loads(response.text)
```
此代码调用URL并传递参数返回一个响应对象。
1. 在此代码下方添加以下代码:
```python
distance = response_body['geometries'][0]['distance']
if distance == 999:
logging.info('Point is outside geofence')
elif distance > 0:
logging.info(f'Point is just outside geofence by a distance of {distance}m')
elif distance == -999:
logging.info(f'Point is inside geofence')
else:
logging.info(f'Point is just inside geofence by a distance of {distance}m')
```
此代码假设只有一个几何体,并从该几何体中提取距离。然后根据距离记录不同的消息。
1. 运行此代码。你将在日志输出中看到GPS坐标是否在地理围栏内或外如果点在50米范围内还会显示距离。使用不同的地理围栏测试此代码根据你的GPS传感器的位置尝试移动传感器例如通过手机WiFi连接或在虚拟IoT设备上使用不同的坐标以查看变化。
1. 准备好后将此代码部署到云端的Functions应用。别忘了部署新的应用程序设置。
> ⚠️ 如果需要,可以参考[项目2课程5中上传应用程序设置的说明](../../../2-farm/lessons/5-migrate-application-to-the-cloud/README.md#task---upload-your-application-settings)。
> ⚠️ 如果需要,可以参考[项目2课程5中部署Functions应用的说明](../../../2-farm/lessons/5-migrate-application-to-the-cloud/README.md#task---deploy-your-functions-app-to-the-cloud)。
> 💁 你可以在 [code/functions](../../../../../3-transport/lessons/4-geofences/code/functions) 文件夹中找到此代码。
---
## 🚀 挑战
在本课程中你使用一个包含单个多边形的GeoJSON文件添加了一个地理围栏。你可以同时上传多个多边形只要它们在 `properties` 部分中有不同的 `geometryId` 值。
尝试上传一个包含多个多边形的GeoJSON文件并调整你的代码以找到GPS坐标最接近或位于哪个多边形内。
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/28)
## 复习与自学
* 阅读更多关于地理围栏及其一些使用案例的信息:[维基百科上的地理围栏页面](https://en.wikipedia.org/wiki/Geo-fence)。
* 阅读更多关于Azure Maps地理围栏API的信息[Microsoft Azure Maps Spatial - Get Geofence文档](https://docs.microsoft.com/rest/api/maps/spatial/getgeofence?WT.mc_id=academic-17441-jabenn)。
* 阅读更多关于消费组的信息:[Azure Event Hubs中的功能和术语 - Microsoft文档上的事件消费者部分](https://docs.microsoft.com/azure/event-hubs/event-hubs-features?WT.mc_id=academic-17441-jabenn#event-consumers)。
## 作业
[使用Twilio发送通知](assignment.md)
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,21 @@
# 使用 Twilio 发送通知
## 说明
在目前的代码中,你只是记录了到地理围栏的距离。在本次任务中,当 GPS 坐标位于地理围栏内时,你需要添加一个通知,可以是短信或电子邮件。
Azure Functions 提供了多种绑定选项,包括与第三方服务的集成,例如 Twilio一个通信平台。
* 在 [Twilio.com](https://www.twilio.com) 注册一个免费账户
* 阅读 [Microsoft 文档 Twilio 绑定 Azure Functions 页面](https://docs.microsoft.com/azure/azure-functions/functions-bindings-twilio?WT.mc_id=academic-17441-jabenn&tabs=python) 上关于将 Azure Functions 绑定到 Twilio SMS 的文档。
* 阅读 [Microsoft 文档 Azure Functions SendGrid 绑定页面](https://docs.microsoft.com/azure/azure-functions/functions-bindings-sendgrid?WT.mc_id=academic-17441-jabenn&tabs=python) 上关于将 Azure Functions 绑定到 Twilio SendGrid 以发送电子邮件的文档。
* 将绑定添加到你的 Functions 应用中,以便在 GPS 坐标位于地理围栏内或外时收到通知——但不能同时通知两种情况。
## 评分标准
| 标准 | 卓越 | 合格 | 需要改进 |
| -------- | --------- | -------- | ----------------- |
| 配置函数绑定并接收电子邮件或短信 | 能够配置函数绑定,并在地理围栏内或外时收到电子邮件或短信,但不能同时通知两种情况 | 能够配置绑定,但无法发送电子邮件或短信,或者只能在坐标同时位于地理围栏内和外时发送 | 无法配置绑定并发送电子邮件或短信 |
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,27 @@
# 制造与加工 - 使用物联网提升食品加工效率
当食品到达中央集散中心或加工厂时,它并不总是直接运送到超市。很多时候,食品需要经过一系列加工步骤,例如按质量进行分拣。这曾经是一个手工完成的过程——从田间开始,采摘工人只会采摘成熟的水果,然后在工厂里,水果会通过传送带,员工手动挑出任何有碰伤或腐烂的水果。我自己在上学期间的暑假曾经做过采摘和分拣草莓的工作,可以证明这并不是一份有趣的工作。
更现代的系统依赖物联网进行分拣。一些早期的设备,比如 [Weco](https://wecotek.com) 的分拣机,使用光学传感器来检测农产品的质量,例如剔除青色的西红柿。这些设备可以部署在农场的收割机上,也可以部署在加工厂中。
随着人工智能AI和机器学习ML的进步这些机器可以变得更加先进使用经过训练的机器学习模型来区分水果和异物例如石头、泥土或昆虫。这些模型还可以被训练来检测水果的质量不仅是发现碰伤的水果还可以早期检测疾病或其他作物问题。
> 🎓 *ML模型* 这个术语指的是在一组数据上训练机器学习软件后得到的输出。例如你可以训练一个ML模型来区分成熟和未成熟的西红柿然后用这个模型对新图像进行判断看看西红柿是否成熟。
在这4节课程中你将学习如何训练基于图像的AI模型来检测水果质量如何在物联网设备上使用这些模型以及如何在边缘设备上运行这些模型——也就是说在物联网设备上运行而不是在云端运行。
> 💁 这些课程会使用一些云资源。如果你没有完成本项目中的所有课程,请确保你[清理你的项目](../clean-up.md)。
## 主题
1. [训练一个水果质量检测器](./lessons/1-train-fruit-detector/README.md)
1. [从物联网设备检查水果质量](./lessons/2-check-fruit-from-device/README.md)
1. [在边缘设备上运行水果检测器](./lessons/3-run-fruit-detector-edge/README.md)
1. [通过传感器触发水果质量检测](./lessons/4-trigger-fruit-detector/README.md)
## 致谢
所有课程由 [Jen Fox](https://github.com/jenfoxbot) 和 [Jim Bennett](https://GitHub.com/JimBobBennett) 倾情编写 ♥️
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,233 @@
# 训练水果质量检测器
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-15.843d21afdc6fb2bba70cd9db7b7d2f91598859fafda2078b0bdc44954194b6c0.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看大图。
这段视频概述了 Azure Custom Vision 服务,这是本课将要介绍的服务。
[![Custom Vision 让机器学习变得简单 | The Xamarin Show](https://img.youtube.com/vi/TETcDLJlWR4/0.jpg)](https://www.youtube.com/watch?v=TETcDLJlWR4)
> 🎥 点击上方图片观看视频
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/29)
## 简介
近年来人工智能AI和机器学习ML的兴起为当今的开发者提供了广泛的能力。通过训练机器学习模型可以识别图像中的不同事物包括未成熟的水果。这些技术可以应用于物联网设备帮助在收获或工厂、仓库的加工过程中对农产品进行分类。
在本课中,您将学习图像分类——使用机器学习模型区分不同事物的图像。您将学习如何训练一个图像分类器,以区分优质水果和劣质水果,例如未成熟、过熟、碰伤或腐烂的水果。
本课内容包括:
* [使用 AI 和 ML 对食品进行分类](../../../../../4-manufacturing/lessons/1-train-fruit-detector)
* [通过机器学习进行图像分类](../../../../../4-manufacturing/lessons/1-train-fruit-detector)
* [训练图像分类器](../../../../../4-manufacturing/lessons/1-train-fruit-detector)
* [测试您的图像分类器](../../../../../4-manufacturing/lessons/1-train-fruit-detector)
* [重新训练您的图像分类器](../../../../../4-manufacturing/lessons/1-train-fruit-detector)
## 使用 AI 和 ML 对食品进行分类
养活全球人口是一项艰巨的任务,尤其是在确保食品价格对所有人都负担得起的情况下。劳动力成本是其中最大的开支之一,因此农民越来越多地转向自动化和物联网等工具来降低劳动力成本。手工收割劳动强度大(且常常是非常辛苦的工作),在富裕国家尤其被机械化所取代。然而,尽管使用机械收割降低了成本,但也带来了一个问题——无法在收割时对食品进行分类。
并非所有作物都会均匀成熟。例如,西红柿在大部分成熟时,藤上可能仍有一些绿色果实。尽管提前收割这些果实是一种浪费,但对农民来说,用机械一次性收割所有果实并在之后处理未成熟的果实更便宜、更方便。
✅ 观察一下您附近农场或花园中生长的水果或蔬菜,或者商店里的水果或蔬菜。它们是否都成熟度一致,还是存在差异?
自动化收割的兴起将农产品的分类从田间转移到了工厂。在工厂中,食品通过长长的传送带运输,由人工团队挑选出不符合质量标准的产品。尽管机械化收割降低了收割成本,但人工分类仍然需要一定的费用。
![如果检测到红色西红柿,它会继续前进。如果检测到绿色西红柿,杠杆会将其弹入废料箱](../../../../../translated_images/zh-CN/optical-tomato-sorting.61aa134bdda4e5b1bfb16a212c1e35a6ef0c426cbb8b1c975f79d7bfbf48d068.png)
下一步的进化是使用机器进行分类,这些机器可以内置在收割机中或位于加工厂中。第一代此类机器使用光学传感器检测颜色,通过控制执行器将绿色西红柿用杠杆或气流弹入废料箱,而红色西红柿则继续沿传送带网络前进。
在这段视频中,当西红柿从一个传送带掉到另一个传送带时,绿色西红柿会被检测到并用杠杆弹入废料箱。
✅ 在工厂或田间,这些光学传感器需要什么样的条件才能正常工作?
最新一代的分类机器利用了 AI 和 ML 的优势,使用经过训练的模型来区分优质和劣质农产品。不仅可以通过明显的颜色差异(如绿色西红柿与红色西红柿)进行区分,还可以通过更细微的外观差异(如疾病或碰伤)来判断。
## 通过机器学习进行图像分类
传统编程是将数据与算法结合,生成输出。例如,在上一个项目中,您将 GPS 坐标和地理围栏作为输入,应用 Azure Maps 提供的算法,得到点是否在地理围栏内的结果。输入更多数据,就会得到更多输出。
![传统开发使用输入和算法生成输出。机器学习使用输入和输出数据训练模型,该模型可以用新输入数据生成新输出](../../../../../translated_images/zh-CN/traditional-vs-ml.5c20c169621fa539.webp)
机器学习则颠倒了这一过程——您从数据和已知输出开始,机器学习算法从数据中学习。然后,您可以将训练好的算法(称为*机器学习模型*或*模型*)应用于新数据,生成新的输出。
> 🎓 机器学习算法从数据中学习的过程称为*训练*。输入和已知输出称为*训练数据*。
例如,您可以向模型提供数百万张未成熟香蕉的图片作为输入训练数据,并将训练输出设置为 `未成熟`,同时提供数百万张成熟香蕉的图片作为训练数据,并将输出设置为 `成熟`。机器学习算法会根据这些数据创建一个模型。然后,您可以向该模型提供一张新的香蕉图片,它会预测这张图片中的香蕉是成熟还是未成熟。
> 🎓 机器学习模型的结果称为*预测*
![两根香蕉,一根成熟的预测为 99.7% 成熟0.3% 未成熟;另一根未成熟的预测为 1.4% 成熟98.6% 未成熟](../../../../../translated_images/zh-CN/bananas-ripe-vs-unripe-predictions.8d0e2034014aa50ece4e4589e724b142da0681f35470fe3db3f7d51240f69c85.png)
机器学习模型不会给出二元答案,而是提供概率。例如,一个模型可能会对一张香蕉图片预测 `成熟` 的概率为 99.7%`未成熟` 的概率为 0.3%。您的代码会选择最优预测,并判断这根香蕉是成熟的。
用于检测图像的机器学习模型称为*图像分类器*——它接收带标签的图像,然后根据这些标签对新图像进行分类。
> 💁 这是一个简化的描述,还有许多其他训练模型的方法,例如无需带标签输出的无监督学习。如果您想了解更多关于机器学习的知识,可以查看 [机器学习初学者课程,一个包含 24 节课的机器学习课程](https://aka.ms/ML-beginners)。
## 训练图像分类器
要成功训练一个图像分类器,您需要数百万张图片。然而,事实证明,一旦您拥有一个经过数百万或数十亿张各种图片训练的图像分类器,您可以通过一个称为*迁移学习*的过程,使用少量图片重新训练它,并获得出色的结果。
> 🎓 迁移学习是将现有机器学习模型的学习成果转移到基于新数据的新模型中。
一旦图像分类器经过广泛的图片训练,其内部结构就非常擅长识别形状、颜色和模式。迁移学习允许模型利用其已经学会的图像部分识别能力,来识别新图像。
![一旦您能识别形状,它们可以以不同的配置组成一艘船或一只猫](../../../../../translated_images/zh-CN/shapes-to-images.1a309f0ea88dd66f.webp)
您可以将其类比为儿童的形状书,一旦您能识别半圆形、矩形和三角形,您就能根据这些形状的配置识别出一艘帆船或一只猫。图像分类器可以识别这些形状,而迁移学习则教会它什么样的组合是船或猫——或者是成熟的香蕉。
有许多工具可以帮助您完成这一过程,包括可以帮助您训练模型并通过 Web API 使用它的基于云的服务。
> 💁 训练这些模型需要大量的计算能力通常通过图形处理单元GPU实现。与让 Xbox 游戏画面看起来惊艳的硬件类似GPU 也可以用来训练机器学习模型。通过云服务,您可以租用配备 GPU 的强大计算机来训练这些模型,仅在需要时使用所需的计算能力。
## Custom Vision
Custom Vision 是一个基于云的工具,用于训练图像分类器。它允许您仅使用少量图片训练分类器。您可以通过 Web 门户、Web API 或 SDK 上传图片,并为每张图片添加一个*标签*,表示该图片的分类。然后,您可以训练模型并测试其性能。一旦对模型满意,您可以发布其版本,通过 Web API 或 SDK 访问。
![Azure Custom Vision 标志](../../../../../translated_images/zh-CN/custom-vision-logo.d3d4e7c8a87ec9daf825e72e210576c3cbf60312577be7a139e22dd97ab7f1e6.png)
> 💁 您可以用每个分类仅 5 张图片训练一个 Custom Vision 模型,但图片越多效果越好。至少 30 张图片可以获得更好的结果。
Custom Vision 是 Microsoft 提供的一系列 AI 工具(称为 Cognitive Services的一部分。这些 AI 工具可以无需训练或仅需少量训练即可使用,包括语音识别与翻译、语言理解和图像分析。这些服务在 Azure 中提供免费层。
> 💁 免费层足以创建模型、训练它并用于开发工作。您可以在 [Microsoft 文档上的 Custom Vision 限制和配额页面](https://docs.microsoft.com/azure/cognitive-services/custom-vision-service/limits-and-quotas?WT.mc_id=academic-17441-jabenn) 阅读免费层的限制。
### 任务 - 创建认知服务资源
要使用 Custom Vision您需要先使用 Azure CLI 在 Azure 中创建两个认知服务资源,一个用于 Custom Vision 训练,一个用于 Custom Vision 预测。
1. 为此项目创建一个名为 `fruit-quality-detector` 的资源组。
1. 使用以下命令创建一个免费的 Custom Vision 训练资源:
```sh
az cognitiveservices account create --name fruit-quality-detector-training \
--resource-group fruit-quality-detector \
--kind CustomVision.Training \
--sku F0 \
--yes \
--location <location>
```
`<location>` 替换为创建资源组时使用的位置。
这将会在您的资源组中创建一个 Custom Vision 训练资源。它将被命名为 `fruit-quality-detector-training`,并使用 `F0` SKU免费层。`--yes` 选项表示您同意认知服务的条款和条件。
> 💁 如果您已经在任何认知服务中使用了免费账户,请使用 `S0` SKU。
1. 使用以下命令创建一个免费的 Custom Vision 预测资源:
```sh
az cognitiveservices account create --name fruit-quality-detector-prediction \
--resource-group fruit-quality-detector \
--kind CustomVision.Prediction \
--sku F0 \
--yes \
--location <location>
```
`<location>` 替换为创建资源组时使用的位置。
这将会在您的资源组中创建一个 Custom Vision 预测资源。它将被命名为 `fruit-quality-detector-prediction`,并使用 `F0` SKU免费层。`--yes` 选项表示您同意认知服务的条款和条件。
### 任务 - 创建图像分类器项目
1. 打开 [CustomVision.ai](https://customvision.ai) 的 Custom Vision 门户,并使用您的 Azure 账户关联的 Microsoft 账户登录。
1. 按照 [Microsoft 文档中构建分类器快速入门的“创建新项目”部分](https://docs.microsoft.com/azure/cognitive-services/custom-vision-service/getting-started-build-a-classifier?WT.mc_id=academic-17441-jabenn#create-a-new-project) 创建一个新的 Custom Vision 项目。UI 可能会有所变化,这些文档始终是最新的参考。
将您的项目命名为 `fruit-quality-detector`
创建项目时,请确保使用之前创建的 `fruit-quality-detector-training` 资源。选择*分类*项目类型、*多分类*分类类型,并选择*食品*领域。
![Custom Vision 项目的设置,名称为 fruit-quality-detector无描述资源为 fruit-quality-detector-training项目类型为分类分类类型为多分类领域为食品](../../../../../translated_images/zh-CN/custom-vision-create-project.cf46325b92d8b131089f6647cf5e07b664cb77850e106d66e3c057b6b69756c6.png)
✅ 花些时间探索您的图像分类器的 Custom Vision UI。
### 任务 - 训练您的图像分类器项目
要训练图像分类器,您需要多张水果的图片,包括优质和劣质的水果图片,并为其添加标签,例如成熟和过熟的香蕉。
💁 这些分类器可以对任何图像进行分类,所以如果你手头没有质量不同的水果,你可以用两种不同类型的水果,或者猫和狗!
理想情况下,每张图片应该只包含水果,背景要么保持一致,要么有多种背景。确保背景中没有任何与成熟或未成熟水果相关的特定内容。
> 💁 重要的是不要使用特定的背景或与分类标签无关的物品否则分类器可能会根据背景进行分类。例如有一个皮肤癌分类器它被训练用来区分正常和癌变的痣而癌变的痣旁边总是有尺子用来测量大小。结果发现这个分类器几乎100%准确地识别图片中的尺子,而不是癌变的痣。
图像分类器运行时分辨率非常低。例如Custom Vision可以处理最大10240x10240的训练和预测图像但模型实际上是在227x227的图像上训练和运行的。较大的图像会被缩小到这个尺寸因此确保你要分类的物体在图像中占据较大的部分否则在分类器使用的小图像中可能会太小。
1. 收集分类器所需的图片。每个标签至少需要5张图片来训练分类器但越多越好。此外还需要一些额外的图片来测试分类器。这些图片应该是同一物体的不同图片。例如
* 使用2个成熟的香蕉从不同角度为每个香蕉拍摄几张图片至少拍摄7张5张用于训练2张用于测试但理想情况下更多。
![两根不同香蕉的照片](../../../../../translated_images/zh-CN/banana-training-images.530eb203346d73bc23b8b990fb4609470bf4ff7c942ccc13d4cfffeed9be1ad4.png)
* 对2个未成熟的香蕉重复相同的过程。
你应该至少有10张训练图片其中至少5张成熟的5张未成熟的以及4张测试图片2张成熟的2张未成熟的。图片格式应为png或jpeg大小小于6MB。如果你使用iPhone拍摄它们可能是高分辨率的HEIC格式图片因此需要转换并可能缩小。图片越多越好并且成熟和未成熟的图片数量应该相近。
如果你没有成熟和未成熟的水果,可以使用不同的水果,或者任何你手头上的两个物体。你也可以在[images](../../../../../4-manufacturing/lessons/1-train-fruit-detector/images)文件夹中找到一些成熟和未成熟香蕉的示例图片供你使用。
1. 按照[Microsoft文档中构建分类器快速入门的上传和标记图片部分](https://docs.microsoft.com/azure/cognitive-services/custom-vision-service/getting-started-build-a-classifier?WT.mc_id=academic-17441-jabenn#upload-and-tag-images)上传你的训练图片。将成熟的水果标记为`ripe`,未成熟的水果标记为`unripe`。
![上传成熟和未成熟香蕉图片的对话框](../../../../../translated_images/zh-CN/image-upload-bananas.0751639f3815e0ec42bdbc6254d1e4357a185834d1ae10c9948a0e7d6d336695.png)
1. 按照[Microsoft文档中构建分类器快速入门的训练分类器部分](https://docs.microsoft.com/azure/cognitive-services/custom-vision-service/getting-started-build-a-classifier?WT.mc_id=academic-17441-jabenn#train-the-classifier)训练你的图像分类器。
你将会看到训练类型的选择。选择**快速训练**。
分类器将开始训练。训练完成需要几分钟时间。
> 🍌 如果你决定在分类器训练期间吃掉你的水果,请确保你有足够的图片用于测试!
## 测试你的图像分类器
分类器训练完成后,你可以通过提供一张新图片来测试它的分类能力。
### 任务 - 测试你的图像分类器
1. 按照[Microsoft文档中测试模型的文档](https://docs.microsoft.com/azure/cognitive-services/custom-vision-service/test-your-model?WT.mc_id=academic-17441-jabenn#test-your-model)测试你的图像分类器。使用你之前创建的测试图片,而不是任何用于训练的图片。
![一个未成熟香蕉被预测为未成熟概率为98.9%成熟概率为1.1%](../../../../../translated_images/zh-CN/banana-unripe-quick-test-prediction.dae9b5e1c4ef7c64886422438850ea14f0be6ac918c217ea3b255c685abfabe7.png)
1. 尝试使用你所有的测试图片,并观察概率。
## 重新训练你的图像分类器
当你测试分类器时,它可能不会给出你预期的结果。图像分类器使用机器学习根据图像中的特定特征来预测它是否匹配某个标签。这是基于概率的,而不是理解图像中的内容——它不知道什么是香蕉,也不理解香蕉为什么是香蕉而不是船。你可以通过使用分类器错误分类的图片重新训练它来改进分类器。
每次使用快速测试选项进行预测时,图片和结果都会被存储。你可以使用这些图片重新训练你的模型。
### 任务 - 重新训练你的图像分类器
1. 按照[Microsoft文档中使用预测图片进行训练的文档](https://docs.microsoft.com/azure/cognitive-services/custom-vision-service/test-your-model?WT.mc_id=academic-17441-jabenn#use-the-predicted-image-for-training)重新训练你的模型,为每张图片使用正确的标签。
1. 模型重新训练完成后,使用新图片进行测试。
---
## 🚀 挑战
你认为如果使用一张草莓的图片测试一个训练了香蕉的模型会发生什么?或者使用充气香蕉的图片、穿香蕉服装的人的图片,甚至是黄色卡通人物(比如《辛普森一家》中的角色)会发生什么?
试试看,观察预测结果。你可以使用[Bing图片搜索](https://www.bing.com/images/trending)找到图片进行尝试。
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/30)
## 复习与自学
* 当你训练分类器时,你会看到*Precision*(精度)、*Recall*(召回率)和*AP*(平均精度)等值,这些值用于评估创建的模型。通过[Microsoft文档中构建分类器快速入门的评估分类器部分](https://docs.microsoft.com/azure/cognitive-services/custom-vision-service/getting-started-build-a-classifier?WT.mc_id=academic-17441-jabenn#evaluate-the-classifier)了解这些值的含义。
* 阅读[Microsoft文档中如何改进你的Custom Vision模型](https://docs.microsoft.com/azure/cognitive-services/custom-vision-service/getting-started-improving-your-classifier?WT.mc_id=academic-17441-jabenn)以了解如何改进你的分类器。
## 作业
[为多种水果和蔬菜训练你的分类器](assignment.md)
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而产生的任何误解或误读不承担责任。

@ -0,0 +1,19 @@
# 为多种水果和蔬菜训练分类器
## 说明
在本课中,你已经训练了一个图像分类器来区分成熟和未成熟的水果,但仅限于一种水果。分类器可以被训练来识别多种水果,其成功率会因水果种类以及成熟与未成熟之间的差异而有所不同。
例如,对于成熟时会改变颜色的水果,图像分类器可能不如颜色传感器有效,因为图像分类器通常基于灰度图像而非全彩图像。
尝试用其他水果训练你的分类器,看看效果如何,尤其是当水果外观相似时。例如,苹果和番茄。
## 评分标准
| 标准 | 卓越 | 合格 | 需要改进 |
| ---- | ---- | ---- | -------- |
| 为多种水果训练分类器 | 能够成功为多种水果训练分类器 | 能够成功为一种额外的水果训练分类器 | 无法为更多水果训练分类器 |
| 判断分类器的效果 | 能够正确评价分类器在不同水果上的表现 | 能够观察并提出分类器效果的建议 | 无法评价分类器的效果 |
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,161 @@
# 从物联网设备检查水果质量
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-16.215daf18b00631fbdfd64c6fc2dc6044dff5d544288825d8076f9fb83d964c23.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看大图。
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/31)
## 简介
在上一课中,你学习了图像分类器以及如何训练它们来检测优质和劣质水果。要在物联网应用中使用这个图像分类器,你需要能够通过某种摄像头捕获图像,并将图像发送到云端进行分类。
在本课中,你将学习摄像头传感器,以及如何将它们与物联网设备结合使用来捕获图像。同时,你还将学习如何从物联网设备调用图像分类器。
本课内容包括:
* [摄像头传感器](../../../../../4-manufacturing/lessons/2-check-fruit-from-device)
* [使用物联网设备捕获图像](../../../../../4-manufacturing/lessons/2-check-fruit-from-device)
* [发布你的图像分类器](../../../../../4-manufacturing/lessons/2-check-fruit-from-device)
* [从物联网设备分类图像](../../../../../4-manufacturing/lessons/2-check-fruit-from-device)
* [改进模型](../../../../../4-manufacturing/lessons/2-check-fruit-from-device)
## 摄像头传感器
顾名思义,摄像头传感器是可以连接到物联网设备的摄像头。它们可以拍摄静态图像或捕获流媒体视频。有些会返回原始图像数据,而有些会将图像数据压缩成如 JPEG 或 PNG 格式的图像文件。通常,与物联网设备配套的摄像头比你习惯使用的摄像头要小得多,分辨率也较低,但你也可以找到分辨率媲美高端手机的摄像头。你还可以选择各种可更换镜头、多摄像头配置、红外热成像摄像头或紫外线摄像头。
![场景中的光线通过镜头聚焦到 CMOS 传感器上](../../../../../translated_images/zh-CN/cmos-sensor.75f9cd74decb137149a4c9ea825251a4549497d67c0ae2776159e6102bb53aa9.png)
大多数摄像头传感器使用图像传感器,其中每个像素是一个光电二极管。镜头将图像聚焦到图像传感器上,成千上万个光电二极管检测到落在其上的光线,并将其记录为像素数据。
> 💁 镜头会将图像倒置,摄像头传感器会将图像翻转回正确的方向。这与你的眼睛相同——你看到的图像在眼睛后部是倒置的,而你的大脑会将其校正。
> 🎓 图像传感器被称为有源像素传感器APS最常见的 APS 类型是互补金属氧化物半导体传感器,简称 CMOS。你可能听说过 CMOS 传感器这个术语。
摄像头传感器是数字传感器,通过数字数据发送图像数据,通常借助提供通信功能的库。摄像头使用像 SPI 这样的协议连接,以便发送大量数据——图像的数据量远大于温度传感器等传感器的单个数值。
✅ 物联网设备在图像大小方面有哪些限制?请思考特别是微控制器硬件的约束。
## 使用物联网设备捕获图像
你可以使用物联网设备捕获图像以进行分类。
### 任务 - 使用物联网设备捕获图像
按照相关指南,使用你的物联网设备捕获图像:
* [Arduino - Wio Terminal](wio-terminal-camera.md)
* [单板计算机 - 树莓派](pi-camera.md)
* [单板计算机 - 虚拟设备](virtual-device-camera.md)
## 发布你的图像分类器
你在上一课中训练了图像分类器。在从物联网设备使用它之前,你需要发布模型。
### 模型迭代
在上一课中训练模型时,你可能注意到 **性能** 选项卡的侧边显示了迭代。当你第一次训练模型时,会看到 *Iteration 1*。当你使用预测图像改进模型时,会看到 *Iteration 2*
每次训练模型时,都会生成一个新的迭代。这是一种跟踪模型在不同数据集上训练的不同版本的方法。当你进行 **快速测试** 时,可以使用下拉菜单选择迭代,以便比较多个迭代的结果。
当你对某个迭代满意时,可以将其发布,使其可供外部应用使用。这样,你可以有一个已发布的版本供设备使用,同时在多个迭代中改进新版本,直到满意后再发布。
### 任务 - 发布一个迭代
迭代可以通过 Custom Vision 门户发布。
1. 打开 [CustomVision.ai](https://customvision.ai) 并登录(如果尚未打开)。然后打开你的 `fruit-quality-detector` 项目。
1. 从顶部选项中选择 **性能** 选项卡。
1. 从侧边的 *迭代* 列表中选择最新的迭代。
1. 点击该迭代的 **发布** 按钮。
![发布按钮](../../../../../translated_images/zh-CN/custom-vision-publish-button.b7174e1977b0c33b8b72d4e5b1326c779e0af196f3849d09985ee2d7d5493a39.png)
1. 在 *发布模型* 对话框中,将 *预测资源* 设置为你在上一课中创建的 `fruit-quality-detector-prediction` 资源。将名称保留为 `Iteration2`,然后点击 **发布** 按钮。
1. 发布后,点击 **预测 URL** 按钮。这将显示预测 API 的详细信息,你需要这些信息来从物联网设备调用模型。下方部分标记为 *如果你有一个图像文件*,这是你需要的详细信息。复制显示的 URL类似于
```output
https://<location>.api.cognitive.microsoft.com/customvision/v3.0/Prediction/<id>/classify/iterations/Iteration2/image
```
其中 `<location>` 是你创建自定义视觉资源时使用的位置,`<id>` 是由字母和数字组成的长 ID。
同时复制 *预测密钥* 值。这是一个安全密钥,调用模型时必须传递。只有传递此密钥的应用程序才被允许使用模型,其他应用程序将被拒绝。
![预测 API 对话框显示 URL 和密钥](../../../../../translated_images/zh-CN/custom-vision-prediction-key-endpoint.30c569ffd0338864f319911f052d5e9b8c5066cb0800a26dd6f7ff5713130ad8.png)
✅ 当一个新迭代被发布时,它会有一个不同的名称。你认为如何更改物联网设备使用的迭代?
## 从物联网设备分类图像
现在你可以使用这些连接详细信息从物联网设备调用图像分类器。
### 任务 - 从物联网设备分类图像
按照相关指南,使用你的物联网设备分类图像:
* [Arduino - Wio Terminal](wio-terminal-classify-image.md)
* [单板计算机 - 树莓派/虚拟物联网设备](single-board-computer-classify-image.md)
## 改进模型
你可能会发现,使用连接到物联网设备的摄像头时,预测结果与预期不符。预测的准确性可能不如从电脑上传的图像。这是因为模型是用不同的数据训练的,而不是用于预测的数据。
为了获得最佳的图像分类器结果,你需要用与预测图像尽可能相似的图像训练模型。例如,如果你用手机摄像头捕获图像进行训练,图像质量、清晰度和颜色会与物联网设备连接的摄像头不同。
![两张香蕉图片,一张是物联网设备拍摄的低分辨率、光线较差的图片,另一张是手机拍摄的高分辨率、光线良好的图片](../../../../../translated_images/zh-CN/banana-picture-compare.174df164dc326a42cf7fb051a7497e6113c620e91552d92ca914220305d47d9a.png)
在上图中,左边的香蕉图片是用树莓派摄像头拍摄的,右边的图片是用 iPhone 在同一位置拍摄的同一香蕉。可以明显看出质量差异——iPhone 的图片更清晰,颜色更鲜艳,对比度更高。
✅ 还有什么可能导致物联网设备捕获的图像预测不准确?思考物联网设备可能使用的环境,哪些因素会影响图像的捕获?
为了改进模型,你可以使用物联网设备捕获的图像重新训练它。
### 任务 - 改进模型
1. 使用你的物联网设备分类多张成熟和未成熟水果的图像。
1. 在 Custom Vision 门户中,使用 *预测* 选项卡上的图像重新训练模型。
> ⚠️ 如果需要,可以参考 [第 1 课中重新训练分类器的说明](../1-train-fruit-detector/README.md#retrain-your-image-classifier)。
1. 如果你的图像与用于训练的原始图像差异很大,可以通过在 *训练图像* 选项卡中选择它们并点击 **删除** 按钮删除所有原始图像。将光标移到图像上会出现一个勾选框,点击勾选框即可选择或取消选择图像。
1. 训练模型的新迭代,并按照上述步骤发布。
1. 更新代码中的端点 URL并重新运行应用程序。
1. 重复这些步骤,直到对预测结果满意为止。
---
## 🚀 挑战
图像分辨率或光线对预测的影响有多大?
尝试在设备代码中更改图像的分辨率,看看是否会影响图像质量。同时尝试更改光线条件。
如果你要创建一个用于销售给农场或工厂的生产设备,如何确保它始终提供一致的结果?
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/32)
## 复习与自学
你使用门户训练了自定义视觉模型。这依赖于已有的图像——而在现实世界中,你可能无法获得与设备摄像头捕获的图像匹配的训练数据。你可以通过使用训练 API 直接从设备训练模型来解决这个问题,从而使用物联网设备捕获的图像进行训练。
* 阅读 [使用 Custom Vision SDK 快速入门](https://docs.microsoft.com/azure/cognitive-services/custom-vision-service/quickstarts/image-classification?WT.mc_id=academic-17441-jabenn&tabs=visual-studio&pivots=programming-language-python) 中的训练 API。
## 作业
[响应分类结果](assignment.md)
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,16 @@
# 响应分类结果
## 说明
您的设备已经对图像进行了分类,并获得了预测值。设备可以利用这些信息执行某些操作——例如将数据发送到 IoT Hub 供其他系统处理,或者控制一个执行器(如 LED在水果未成熟时点亮。
请为您的设备添加代码,以您选择的方式进行响应——可以是发送数据到 IoT Hub控制一个执行器或者结合两者发送数据到 IoT Hub并通过无服务器代码判断水果是否成熟然后发送指令来控制执行器。
## 评分标准
| 标准 | 卓越 | 合格 | 需要改进 |
| ---- | ---- | ---- | -------- |
| 响应预测结果 | 能够实现对预测结果的响应,并且在相同预测值的情况下能够稳定运行。 | 能够实现一个与预测结果无关的响应,例如仅将原始数据发送到 IoT Hub。 | 无法编程设备以响应预测结果。 |
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们不对因使用此翻译而产生的任何误解或误读承担责任。

@ -0,0 +1,144 @@
# 捕捉图像 - 树莓派
在本课程的这一部分,你将为树莓派添加一个摄像头传感器,并从中读取图像。
## 硬件
树莓派需要一个摄像头。
你将使用的摄像头是 [树莓派摄像头模块](https://www.raspberrypi.org/products/camera-module-v2/)。这个摄像头专为树莓派设计,通过树莓派上的专用连接器连接。
> 💁 这个摄像头使用 [摄像头串行接口Camera Serial Interface一种由移动产业处理器接口联盟开发的协议](https://wikipedia.org/wiki/Camera_Serial_Interface),简称 MIPI-CSI。这是一种专用的图像传输协议。
## 连接摄像头
摄像头可以通过一条扁平电缆连接到树莓派。
### 任务 - 连接摄像头
![树莓派摄像头](../../../../../translated_images/zh-CN/pi-camera-module.4278753c31bd6e757aa2b858be97d72049f71616278cefe4fb5abb485b40a078.png)
1. 关闭树莓派的电源。
1. 将摄像头附带的扁平电缆连接到摄像头上。为此,轻轻拉动插座中的黑色塑料夹,使其稍微弹出,然后将电缆滑入插座,蓝色一面朝向镜头外侧,金属针脚条朝向镜头。插入到位后,将黑色塑料夹推回原位。
你可以在 [树莓派摄像头模块入门文档](https://projects.raspberrypi.org/en/projects/getting-started-with-picamera/2) 中找到一个动画,展示如何打开夹子并插入电缆。
![扁平电缆插入摄像头模块](../../../../../translated_images/zh-CN/pi-camera-ribbon-cable.0bf82acd251611c21ac616f082849413e2b322a261d0e4f8fec344248083b07e.png)
1. 从树莓派上移除 Grove Base Hat。
1. 将扁平电缆穿过 Grove Base Hat 的摄像头插槽。确保电缆的蓝色一面朝向标有 **A0**、**A1** 等的模拟端口。
![扁平电缆穿过 Grove Base Hat](../../../../../translated_images/zh-CN/grove-base-hat-ribbon-cable.501fed202fcf73b11b2b68f6d246189f7d15d3e4423c572ddee79d77b4632b47.png)
1. 将扁平电缆插入树莓派上的摄像头端口。同样,拉起黑色塑料夹,插入电缆,然后将夹子推回。电缆的蓝色一面应朝向 USB 和以太网端口。
![扁平电缆连接到树莓派的摄像头插座](../../../../../translated_images/zh-CN/pi-camera-socket-ribbon-cable.a18309920b11800911082ed7aa6fb28e6d9be3a022e4079ff990016cae3fca10.png)
1. 重新安装 Grove Base Hat。
## 编程摄像头
现在可以使用 [PiCamera](https://pypi.org/project/picamera/) Python 库对树莓派进行编程以使用摄像头。
### 任务 - 启用传统摄像头模式
不幸的是,随着树莓派操作系统 Bullseye 的发布,操作系统附带的摄像头软件发生了变化,默认情况下 PiCamera 不再工作。目前正在开发一个替代方案,名为 PiCamera2但尚未准备好使用。
目前,你可以将树莓派设置为传统摄像头模式,以使 PiCamera 工作。摄像头插座默认也是禁用的,但启用传统摄像头软件会自动启用插座。
1. 启动树莓派并等待其完成启动。
1. 启动 VS Code可以直接在树莓派上运行也可以通过 Remote SSH 扩展连接。
1. 在终端中运行以下命令:
```sh
sudo raspi-config nonint do_legacy 0
sudo reboot
```
这将切换设置以启用传统摄像头软件,然后重启树莓派以使设置生效。
1. 等待树莓派重启,然后重新启动 VS Code。
### 任务 - 编程摄像头
对设备进行编程。
1. 在终端中,在 `pi` 用户的主目录中创建一个名为 `fruit-quality-detector` 的新文件夹。在此文件夹中创建一个名为 `app.py` 的文件。
1. 在 VS Code 中打开此文件夹。
1. 要与摄像头交互,可以使用 PiCamera Python 库。使用以下命令安装 Pip 包:
```sh
pip3 install picamera
```
1. 将以下代码添加到你的 `app.py` 文件中:
```python
import io
import time
from picamera import PiCamera
```
这段代码导入了一些需要的库,包括 `PiCamera` 库。
1. 在此代码下方添加以下代码以初始化摄像头:
```python
camera = PiCamera()
camera.resolution = (640, 480)
camera.rotation = 0
time.sleep(2)
```
这段代码创建了一个 PiCamera 对象,并将分辨率设置为 640x480。虽然支持更高的分辨率最高 3280x2464但图像分类器使用的图像尺寸要小得多227x227因此无需捕获和发送更大的图像。
`camera.rotation = 0` 行设置图像的旋转角度。扁平电缆从摄像头底部进入,但如果你的摄像头旋转过以便更容易对准你想要分类的物品,则可以将此行更改为旋转的角度。
![摄像头悬挂在饮料罐上方](../../../../../translated_images/zh-CN/pi-camera-upside-down.5376961ba31459883362124152ad6b823d5ac5fc14e85f317e22903bd681c2b6.png)
例如,如果你将扁平电缆悬挂在某物上,使其位于摄像头顶部,则将旋转设置为 180
```python
camera.rotation = 180
```
摄像头需要几秒钟启动,因此有 `time.sleep(2)`
1. 在此代码下方添加以下代码以将图像捕获为二进制数据:
```python
image = io.BytesIO()
camera.capture(image, 'jpeg')
image.seek(0)
```
这段代码创建了一个 `BytesIO` 对象来存储二进制数据。图像以 JPEG 文件的形式从摄像头读取并存储在此对象中。此对象有一个位置指示器,用于指示数据中的当前位置,以便需要时可以将更多数据写入末尾,因此 `image.seek(0)` 行将位置移回起始位置,以便稍后可以读取所有数据。
1. 在此代码下方添加以下代码以将图像保存到文件:
```python
with open('image.jpg', 'wb') as image_file:
image_file.write(image.read())
```
这段代码打开一个名为 `image.jpg` 的文件进行写入,然后从 `BytesIO` 对象中读取所有数据并写入该文件。
> 💁 你可以直接将图像捕获到文件,而不是使用 `BytesIO` 对象,只需将文件名传递给 `camera.capture` 调用即可。使用 `BytesIO` 对象的原因是稍后在本课程中你可以将图像发送到图像分类器。
1. 将摄像头对准某物并运行此代码。
1. 图像将被捕获并保存为当前文件夹中的 `image.jpg`。你将在 VS Code 的资源管理器中看到此文件。选择该文件以查看图像。如果需要旋转,请根据需要更新 `camera.rotation = 0` 行并重新拍摄照片。
> 💁 你可以在 [code-camera/pi](../../../../../4-manufacturing/lessons/2-check-fruit-from-device/code-camera/pi) 文件夹中找到此代码。
😀 你的摄像头程序运行成功!
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本作为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,94 @@
# 分类图像 - 虚拟 IoT 硬件和 Raspberry Pi
在本节课程中,您将把摄像头捕获的图像发送到 Custom Vision 服务进行分类。
## 将图像发送到 Custom Vision
Custom Vision 服务提供了一个 Python SDK您可以用它来分类图像。
### 任务 - 将图像发送到 Custom Vision
1. 在 VS Code 中打开 `fruit-quality-detector` 文件夹。如果您使用的是虚拟 IoT 设备,请确保虚拟环境在终端中运行。
1. 用于将图像发送到 Custom Vision 的 Python SDK 可作为一个 Pip 包安装。使用以下命令安装:
```sh
pip3 install azure-cognitiveservices-vision-customvision
```
1. 在 `app.py` 文件的顶部添加以下导入语句:
```python
from msrest.authentication import ApiKeyCredentials
from azure.cognitiveservices.vision.customvision.prediction import CustomVisionPredictionClient
```
这将引入 Custom Vision 库中的一些模块,一个用于使用预测密钥进行身份验证,另一个提供可以调用 Custom Vision 的预测客户端类。
1. 在文件末尾添加以下代码:
```python
prediction_url = '<prediction_url>'
prediction_key = '<prediction key>'
```
`<prediction_url>` 替换为您在本课程前面从 *Prediction URL* 对话框中复制的 URL。将 `<prediction key>` 替换为您从同一对话框中复制的预测密钥。
1. *Prediction URL* 对话框提供的预测 URL 设计用于直接调用 REST 端点。Python SDK 在不同的地方使用 URL 的不同部分。添加以下代码将此 URL 分解为所需的部分:
```python
parts = prediction_url.split('/')
endpoint = 'https://' + parts[2]
project_id = parts[6]
iteration_name = parts[9]
```
这会拆分 URL提取出 `https://<location>.api.cognitive.microsoft.com` 的端点、项目 ID 和已发布迭代的名称。
1. 使用以下代码创建一个预测器对象来执行预测:
```python
prediction_credentials = ApiKeyCredentials(in_headers={"Prediction-key": prediction_key})
predictor = CustomVisionPredictionClient(endpoint, prediction_credentials)
```
`prediction_credentials` 包装了预测密钥。这些随后用于创建一个指向端点的预测客户端对象。
1. 使用以下代码将图像发送到 Custom Vision
```python
image.seek(0)
results = predictor.classify_image(project_id, iteration_name, image)
```
这会将图像回滚到起始位置,然后将其发送到预测客户端。
1. 最后,使用以下代码显示结果:
```python
for prediction in results.predictions:
print(f'{prediction.tag_name}:\t{prediction.probability * 100:.2f}%')
```
这会循环显示所有返回的预测结果,并在终端中显示它们。返回的概率是从 0 到 1 的浮点数,其中 0 表示与标签匹配的概率为 0%1 表示与标签匹配的概率为 100%。
> 💁 图像分类器会返回所有使用过的标签的百分比。每个标签都会有一个图像与该标签匹配的概率。
1. 运行您的代码,将摄像头对准一些水果,或者使用适当的图像集,或者如果使用虚拟 IoT 硬件,则让水果在您的网络摄像头中可见。您将在控制台中看到输出:
```output
(.venv) ➜ fruit-quality-detector python app.py
ripe: 56.84%
unripe: 43.16%
```
您将能够看到拍摄的图像,以及这些值在 Custom Vision 的 **Predictions** 选项卡中显示。
![Custom Vision 中的一根香蕉,预测成熟度为 56.8%,未成熟度为 43.1%](../../../../../translated_images/zh-CN/custom-vision-banana-prediction.30cdff4e1d72db5d9a0be0193790a47c2b387da034e12dc1314dd57ca2131b59.png)
> 💁 您可以在 [code-classify/pi](../../../../../4-manufacturing/lessons/2-check-fruit-from-device/code-classify/pi) 或 [code-classify/virtual-iot-device](../../../../../4-manufacturing/lessons/2-check-fruit-from-device/code-classify/virtual-iot-device) 文件夹中找到此代码。
😀 您的水果质量分类器程序成功了!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,115 @@
# 捕获图像 - 虚拟物联网硬件
在本课程的这一部分,您将为虚拟物联网设备添加一个摄像头传感器,并从中读取图像。
## 硬件
虚拟物联网设备将使用一个模拟摄像头,该摄像头可以发送文件中的图像或来自您的网络摄像头的图像。
### 将摄像头添加到 CounterFit
要使用虚拟摄像头,您需要将其添加到 CounterFit 应用程序中。
#### 任务 - 将摄像头添加到 CounterFit
将摄像头添加到 CounterFit 应用程序。
1. 在您的计算机上创建一个名为 `fruit-quality-detector` 的文件夹,并在其中创建一个名为 `app.py` 的单一文件和一个 Python 虚拟环境,然后添加 CounterFit 的 pip 包。
> ⚠️ 如果需要,您可以参考[第 1 课中创建和设置 CounterFit Python 项目的说明](../../../1-getting-started/lessons/1-introduction-to-iot/virtual-device.md)。
1. 安装一个额外的 Pip 包,用于安装一个 CounterFit shim它可以通过模拟部分 [Picamera Pip 包](https://pypi.org/project/picamera/) 来与摄像头传感器通信。确保您是在激活虚拟环境的终端中安装此包。
```sh
pip install counterfit-shims-picamera
```
1. 确保 CounterFit 网页应用程序正在运行。
1. 创建一个摄像头:
1. 在 *Sensors* 面板的 *Create sensor* 框中,点击 *Sensor type* 下拉框并选择 *Camera*
1. 将 *Name* 设置为 `Picamera`
1. 选择 **Add** 按钮以创建摄像头。
![摄像头设置](../../../../../translated_images/zh-CN/counterfit-create-camera.a5de97f59c0bd3cbe0416d7e89a3cfe86d19fbae05c641c53a91286412af0a34.png)
摄像头将被创建并显示在传感器列表中。
![摄像头已创建](../../../../../translated_images/zh-CN/counterfit-camera.001ec52194c8ee5d3f617173da2c79e1df903d10882adc625cbfc493525125d4.png)
## 编程摄像头
现在可以为虚拟物联网设备编程以使用虚拟摄像头。
### 任务 - 编程摄像头
为设备编程。
1. 确保 `fruit-quality-detector` 应用程序已在 VS Code 中打开。
1. 打开 `app.py` 文件。
1. 在 `app.py` 文件顶部添加以下代码以连接应用程序到 CounterFit
```python
from counterfit_connection import CounterFitConnection
CounterFitConnection.init('127.0.0.1', 5000)
```
1. 在您的 `app.py` 文件中添加以下代码:
```python
import io
from counterfit_shims_picamera import PiCamera
```
此代码导入了一些所需的库,包括来自 counterfit_shims_picamera 库的 `PiCamera` 类。
1. 在此代码下方添加以下代码以初始化摄像头:
```python
camera = PiCamera()
camera.resolution = (640, 480)
camera.rotation = 0
```
此代码创建了一个 PiCamera 对象,并将分辨率设置为 640x480。虽然支持更高的分辨率但图像分类器处理的图像尺寸较小227x227因此无需捕获和发送更大的图像。
`camera.rotation = 0` 行设置图像的旋转角度(以度为单位)。如果需要旋转来自网络摄像头或文件的图像,请根据需要设置。例如,如果您想将网络摄像头中横向模式的香蕉图像更改为纵向模式,请设置 `camera.rotation = 90`
1. 在此代码下方添加以下代码以将图像捕获为二进制数据:
```python
image = io.BytesIO()
camera.capture(image, 'jpeg')
image.seek(0)
```
此代码创建了一个 `BytesIO` 对象来存储二进制数据。图像以 JPEG 文件的形式从摄像头读取并存储在此对象中。此对象有一个位置指示器,用于指示数据的当前位置,以便可以在需要时将更多数据写入末尾,因此 `image.seek(0)` 行将此位置移回起始位置,以便稍后可以读取所有数据。
1. 在此代码下方添加以下代码以将图像保存到文件:
```python
with open('image.jpg', 'wb') as image_file:
image_file.write(image.read())
```
此代码打开一个名为 `image.jpg` 的文件进行写入,然后从 `BytesIO` 对象中读取所有数据并写入该文件。
> 💁 您可以直接将图像捕获到文件,而不是使用 `BytesIO` 对象,只需将文件名传递给 `camera.capture` 调用即可。使用 `BytesIO` 对象的原因是稍后在本课程中,您可以将图像发送到您的图像分类器。
1. 配置 CounterFit 中摄像头将捕获的图像。您可以将 *Source* 设置为 *File*,然后上传一个图像文件,或者将 *Source* 设置为 *WebCam*,图像将从您的网络摄像头捕获。确保在选择图片或网络摄像头后点击 **Set** 按钮。
![CounterFit 中设置为文件的图像源,以及设置为网络摄像头显示一个人手持香蕉的预览](../../../../../translated_images/zh-CN/counterfit-camera-options.eb3bd5150a8e7dffbf24bc5bcaba0cf2cdef95fbe6bbe393695d173817d6b8df.png)
1. 图像将被捕获并保存为当前文件夹中的 `image.jpg`。您将在 VS Code 的资源管理器中看到此文件。选择该文件以查看图像。如果需要旋转,请根据需要更新 `camera.rotation = 0` 行并重新拍摄图片。
> 💁 您可以在 [code-camera/virtual-iot-device](../../../../../4-manufacturing/lessons/2-check-fruit-from-device/code-camera/virtual-iot-device) 文件夹中找到此代码。
😀 您的摄像头程序运行成功!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们不对因使用此翻译而产生的任何误解或误读承担责任。

@ -0,0 +1,464 @@
# 捕获图像 - Wio Terminal
在本课程的这一部分,您将为 Wio Terminal 添加一个摄像头,并从中捕获图像。
## 硬件
Wio Terminal 需要一个摄像头。
您将使用的摄像头是 [ArduCam Mini 2MP Plus](https://www.arducam.com/product/arducam-2mp-spi-camera-b0067-arduino/)。这是一个基于 OV2640 图像传感器的 2 百万像素摄像头。它通过 SPI 接口进行通信以捕获图像,并使用 I2C 配置传感器。
## 连接摄像头
ArduCam 没有 Grove 插座,而是通过 Wio Terminal 的 GPIO 引脚连接到 SPI 和 I2C 总线。
### 任务 - 连接摄像头
连接摄像头。
![ArduCam 传感器](../../../../../translated_images/zh-CN/arducam.20e4e4cbb268296570b5914e20d6c349fc42ddac9ed4e1b9deba2188204eebae.png)
1. ArduCam 底部的引脚需要连接到 Wio Terminal 的 GPIO 引脚。为了更容易找到正确的引脚,请将 Wio Terminal 附带的 GPIO 引脚贴纸贴在引脚周围:
![带有 GPIO 引脚贴纸的 Wio Terminal](../../../../../translated_images/zh-CN/wio-terminal-pin-sticker.b90b1535937b84bd.webp)
1. 使用跳线,进行以下连接:
| ArduCAM 引脚 | Wio Terminal 引脚 | 描述 |
| ------------ | ----------------- | -------------------------------------- |
| CS | 24 (SPI_CS) | SPI 芯片选择 |
| MOSI | 19 (SPI_MOSI) | SPI 控制器输出,外设输入 |
| MISO | 21 (SPI_MISO) | SPI 控制器输入,外设输出 |
| SCK | 23 (SPI_SCLK) | SPI 串行时钟 |
| GND | 6 (GND) | 地线 - 0V |
| VCC | 4 (5V) | 5V 电源 |
| SDA | 3 (I2C1_SDA) | I2C 串行数据 |
| SCL | 5 (I2C1_SCL) | I2C 串行时钟 |
![通过跳线连接到 ArduCam 的 Wio Terminal](../../../../../translated_images/zh-CN/arducam-wio-terminal-connections.a4d5a4049bdb5ab800a2877389fc6ecf5e4ff307e6451ff56c517e6786467d0a.png)
GND 和 VCC 连接为 ArduCam 提供 5V 电源。它运行在 5V而 Grove 传感器运行在 3V。此电源直接来自为设备供电的 USB-C 连接。
> 💁 对于 SPI 连接ArduCam 上的引脚标签和 Wio Terminal 引脚名称在代码中仍使用旧命名约定。本课程中的说明将使用新命名约定,除非代码中使用了引脚名称。
1. 现在可以将 Wio Terminal 连接到您的计算机。
## 编程设备以连接摄像头
现在可以对 Wio Terminal 进行编程,以使用连接的 ArduCAM 摄像头。
### 任务 - 编程设备以连接摄像头
1. 使用 PlatformIO 创建一个全新的 Wio Terminal 项目。将此项目命名为 `fruit-quality-detector`。在 `setup` 函数中添加代码以配置串口。
1. 添加代码以连接到 WiFi并将您的 WiFi 凭据放在名为 `config.h` 的文件中。不要忘记将所需的库添加到 `platformio.ini` 文件中。
1. ArduCam 库不是可以从 `platformio.ini` 文件中安装的 Arduino 库。相反,它需要从其 GitHub 页面安装源代码。您可以通过以下方式获取:
* 从 [https://github.com/ArduCAM/Arduino.git](https://github.com/ArduCAM/Arduino.git) 克隆仓库
* 前往 GitHub 上的仓库 [github.com/ArduCAM/Arduino](https://github.com/ArduCAM/Arduino),并从 **Code** 按钮下载代码的 zip 文件
1. 您只需要代码中的 `ArduCAM` 文件夹。将整个文件夹复制到项目中的 `lib` 文件夹中。
> ⚠️ 必须复制整个文件夹,因此代码位于 `lib/ArduCam` 中。不要仅将 `ArduCam` 文件夹的内容复制到 `lib` 文件夹中,而是复制整个文件夹。
1. ArduCam 库代码适用于多种类型的摄像头。您想要使用的摄像头类型通过编译器标志进行配置——这使得构建的库尽可能小,通过移除您未使用的摄像头的代码来实现。要将库配置为 OV2640 摄像头,请在 `platformio.ini` 文件末尾添加以下内容:
```ini
build_flags =
-DARDUCAM_SHIELD_V2
-DOV2640_CAM
```
这设置了两个编译器标志:
* `ARDUCAM_SHIELD_V2` 告诉库摄像头位于 Arduino 板上,称为 shield。
* `OV2640_CAM` 告诉库仅包含 OV2640 摄像头的代码。
1. 在 `src` 文件夹中添加一个名为 `camera.h` 的头文件。此文件将包含与摄像头通信的代码。将以下代码添加到此文件中:
```cpp
#pragma once
#include <ArduCAM.h>
#include <Wire.h>
class Camera
{
public:
Camera(int format, int image_size) : _arducam(OV2640, PIN_SPI_SS)
{
_format = format;
_image_size = image_size;
}
bool init()
{
// Reset the CPLD
_arducam.write_reg(0x07, 0x80);
delay(100);
_arducam.write_reg(0x07, 0x00);
delay(100);
// Check if the ArduCAM SPI bus is OK
_arducam.write_reg(ARDUCHIP_TEST1, 0x55);
if (_arducam.read_reg(ARDUCHIP_TEST1) != 0x55)
{
return false;
}
// Change MCU mode
_arducam.set_mode(MCU2LCD_MODE);
uint8_t vid, pid;
// Check if the camera module type is OV2640
_arducam.wrSensorReg8_8(0xff, 0x01);
_arducam.rdSensorReg8_8(OV2640_CHIPID_HIGH, &vid);
_arducam.rdSensorReg8_8(OV2640_CHIPID_LOW, &pid);
if ((vid != 0x26) && ((pid != 0x41) || (pid != 0x42)))
{
return false;
}
_arducam.set_format(_format);
_arducam.InitCAM();
_arducam.OV2640_set_JPEG_size(_image_size);
_arducam.OV2640_set_Light_Mode(Auto);
_arducam.OV2640_set_Special_effects(Normal);
delay(1000);
return true;
}
void startCapture()
{
_arducam.flush_fifo();
_arducam.clear_fifo_flag();
_arducam.start_capture();
}
bool captureReady()
{
return _arducam.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK);
}
bool readImageToBuffer(byte **buffer, uint32_t &buffer_length)
{
if (!captureReady()) return false;
// Get the image file length
uint32_t length = _arducam.read_fifo_length();
buffer_length = length;
if (length >= MAX_FIFO_SIZE)
{
return false;
}
if (length == 0)
{
return false;
}
// create the buffer
byte *buf = new byte[length];
uint8_t temp = 0, temp_last = 0;
int i = 0;
uint32_t buffer_pos = 0;
bool is_header = false;
_arducam.CS_LOW();
_arducam.set_fifo_burst();
while (length--)
{
temp_last = temp;
temp = SPI.transfer(0x00);
//Read JPEG data from FIFO
if ((temp == 0xD9) && (temp_last == 0xFF)) //If find the end ,break while,
{
buf[buffer_pos] = temp;
buffer_pos++;
i++;
_arducam.CS_HIGH();
}
if (is_header == true)
{
//Write image data to buffer if not full
if (i < 256)
{
buf[buffer_pos] = temp;
buffer_pos++;
i++;
}
else
{
_arducam.CS_HIGH();
i = 0;
buf[buffer_pos] = temp;
buffer_pos++;
i++;
_arducam.CS_LOW();
_arducam.set_fifo_burst();
}
}
else if ((temp == 0xD8) & (temp_last == 0xFF))
{
is_header = true;
buf[buffer_pos] = temp_last;
buffer_pos++;
i++;
buf[buffer_pos] = temp;
buffer_pos++;
i++;
}
}
_arducam.clear_fifo_flag();
_arducam.set_format(_format);
_arducam.InitCAM();
_arducam.OV2640_set_JPEG_size(_image_size);
// return the buffer
*buffer = buf;
}
private:
ArduCAM _arducam;
int _format;
int _image_size;
};
```
这是使用 ArduCam 库配置摄像头并在需要时通过 SPI 总线提取图像的底层代码。此代码非常特定于 ArduCam因此您无需担心它的工作原理。
1. 在 `main.cpp` 中,在其他 `include` 语句下方添加以下代码以包含此新文件并创建摄像头类的实例:
```cpp
#include "camera.h"
Camera camera = Camera(JPEG, OV2640_640x480);
```
这会创建一个 `Camera`,以 640x480 的分辨率保存图像为 JPEG。虽然支持更高的分辨率最高 3280x2464但图像分类器处理的图像尺寸要小得多227x227因此无需捕获和发送更大的图像。
1. 在此下方添加以下代码以定义一个设置摄像头的函数:
```cpp
void setupCamera()
{
pinMode(PIN_SPI_SS, OUTPUT);
digitalWrite(PIN_SPI_SS, HIGH);
Wire.begin();
SPI.begin();
if (!camera.init())
{
Serial.println("Error setting up the camera!");
}
}
```
`setupCamera` 函数首先将 SPI 芯片选择引脚 (`PIN_SPI_SS`) 配置为高电平,使 Wio Terminal 成为 SPI 控制器。然后启动 I2C 和 SPI 总线。最后,它初始化摄像头类,配置摄像头传感器设置并确保所有连接正确。
1. 在 `setup` 函数末尾调用此函数:
```cpp
setupCamera();
```
1. 构建并上传此代码,并检查串口监视器的输出。如果看到 `Error setting up the camera!`,请检查布线以确保所有电缆正确连接 ArduCam 的引脚和 Wio Terminal 的 GPIO 引脚,并且所有跳线电缆都正确插入。
## 捕获图像
现在可以对 Wio Terminal 进行编程,以在按下按钮时捕获图像。
### 任务 - 捕获图像
1. 微控制器会连续运行您的代码因此很难触发类似拍照的操作而不响应传感器。Wio Terminal 有按钮,因此可以设置摄像头通过其中一个按钮触发。将以下代码添加到 `setup` 函数末尾,以配置 C 按钮(顶部的三个按钮之一,靠近电源开关的那个)。
![靠近电源开关的 C 按钮](../../../../../translated_images/zh-CN/wio-terminal-c-button.73df3cb1c1445ea0.webp)
```cpp
pinMode(WIO_KEY_C, INPUT_PULLUP);
```
`INPUT_PULLUP` 模式实际上会反转输入。例如,通常按钮在未按下时会发送低信号,按下时发送高信号。当设置为 `INPUT_PULLUP` 时,它们在未按下时发送高信号,按下时发送低信号。
1. 在 `loop` 函数之前添加一个空函数以响应按钮按下:
```cpp
void buttonPressed()
{
}
```
1. 在按钮按下时在 `loop` 方法中调用此函数:
```cpp
void loop()
{
if (digitalRead(WIO_KEY_C) == LOW)
{
buttonPressed();
delay(2000);
}
delay(200);
}
```
此代码检查按钮是否被按下。如果按下,则调用 `buttonPressed` 函数,并且循环延迟 2 秒。这是为了给按钮释放留出时间,以免长按被注册为两次按下。
> 💁 Wio Terminal 上的按钮设置为 `INPUT_PULLUP`,因此在未按下时发送高信号,按下时发送低信号。
1. 将以下代码添加到 `buttonPressed` 函数中:
```cpp
camera.startCapture();
while (!camera.captureReady())
delay(100);
Serial.println("Image captured");
byte *buffer;
uint32_t length;
if (camera.readImageToBuffer(&buffer, length))
{
Serial.print("Image read to buffer with length ");
Serial.println(length);
delete(buffer);
}
```
此代码通过调用 `startCapture` 开始摄像头捕获。摄像头硬件不会在您请求时返回数据,而是发送指令开始捕获,摄像头将在后台工作以捕获图像、将其转换为 JPEG并将其存储在摄像头本地缓冲区中。然后通过 `captureReady` 调用检查图像捕获是否完成。
捕获完成后,图像数据通过 `readImageToBuffer` 调用从摄像头缓冲区复制到本地缓冲区(字节数组)。缓冲区的长度随后发送到串口监视器。
1. 构建并上传此代码,并检查串口监视器上的输出。每次按下 C 按钮时,都会捕获一张图像,并在串口监视器上看到图像大小。
```output
Connecting to WiFi..
Connected!
Image captured
Image read to buffer with length 9224
Image captured
Image read to buffer with length 11272
```
不同的图像会有不同的大小。它们被压缩为 JPEG 文件,给定分辨率的 JPEG 文件大小取决于图像内容。
> 💁 您可以在 [code-camera/wio-terminal](../../../../../4-manufacturing/lessons/2-check-fruit-from-device/code-camera/wio-terminal) 文件夹中找到此代码。
😀 您已成功使用 Wio Terminal 捕获图像。
## 可选 - 使用 SD 卡验证摄像头图像
查看摄像头捕获的图像的最简单方法是将它们写入 Wio Terminal 中的 SD 卡,然后在计算机上查看。如果您有备用的 microSD 卡和计算机上的 microSD 卡插槽或适配器,请执行此步骤。
Wio Terminal 仅支持最大 16GB 的 microSD 卡。如果您有更大的 SD 卡,则无法使用。
### 任务 - 使用 SD 卡验证摄像头图像
1. 使用计算机上的相关应用程序macOS 上的磁盘工具、Windows 上的文件资源管理器或 Linux 中的命令行工具)将 microSD 卡格式化为 FAT32 或 exFAT。
1. 将 microSD 卡插入电源开关下方的插槽。确保完全插入直到卡扣住并保持到位,您可能需要使用指甲或细工具推动。
1. 在 `main.cpp` 文件顶部添加以下 include 语句:
```cpp
#include "SD/Seeed_SD.h"
#include <Seeed_FS.h>
```
1. 在 `setup` 函数之前添加以下函数:
```cpp
void setupSDCard()
{
while (!SD.begin(SDCARD_SS_PIN, SDCARD_SPI))
{
Serial.println("SD Card Error");
}
}
```
此函数使用 SPI 总线配置 SD 卡。
1. 从 `setup` 函数调用此函数:
```cpp
setupSDCard();
```
1. 在 `buttonPressed` 函数上方添加以下代码:
```cpp
int fileNum = 1;
void saveToSDCard(byte *buffer, uint32_t length)
{
char buff[16];
sprintf(buff, "%d.jpg", fileNum);
fileNum++;
File outFile = SD.open(buff, FILE_WRITE );
outFile.write(buffer, length);
outFile.close();
Serial.print("Image written to file ");
Serial.println(buff);
}
```
这定义了一个用于文件计数的全局变量。此变量用于图像文件名,以便可以捕获多个图像并递增文件名——例如 `1.jpg`、`2.jpg` 等。
然后定义 `saveToSDCard` 函数,该函数接收字节数据缓冲区和缓冲区长度。使用文件计数创建文件名,并为下一个文件递增文件计数。然后将缓冲区中的二进制数据写入文件。
1. 在 `buttonPressed` 函数中调用 `saveToSDCard` 函数。调用应在缓冲区被删除之前:
```cpp
Serial.print("Image read to buffer with length ");
Serial.println(length);
saveToSDCard(buffer, length);
delete(buffer);
```
1. 构建并上传此代码,并检查串口监视器上的输出。每次按下 C 按钮时,都会捕获图像并保存到 SD 卡。
```output
Connecting to WiFi..
Connected!
Image captured
Image read to buffer with length 16392
Image written to file 1.jpg
Image captured
Image read to buffer with length 14344
Image written to file 2.jpg
```
1. 关闭 microSD 卡并通过轻轻按下并释放将其弹出,它会弹出。您可能需要使用细工具执行此操作。将 microSD 卡插入计算机以查看图像。
![使用 ArduCam 捕获的香蕉图片](../../../../../translated_images/zh-CN/banana-arducam.be1b32d4267a8194b0fd042362e56faa431da9cd4af172051b37243ea9be0256.jpg)
💁 相机的白平衡可能需要几张图片来进行自我调整。您会根据捕获的图片颜色注意到这一点,前几张可能颜色不对。您可以通过更改代码,在 `setup` 函数中捕获几张被忽略的图片来解决这个问题。
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,218 @@
# 分类图像 - Wio Terminal
在本节课程中,您将把摄像头捕获的图像发送到 Custom Vision 服务进行分类。
## 分类图像
Custom Vision 服务提供了一个 REST API您可以通过 Wio Terminal 调用该 API 来分类图像。这个 REST API 是通过 HTTPS 连接访问的——一种安全的 HTTP 连接。
在与 HTTPS 端点交互时,客户端代码需要从被访问的服务器请求公钥证书,并使用该证书加密发送的流量。您的网页浏览器会自动完成这些操作,但微控制器不会。您需要手动请求此证书,并使用它来创建与 REST API 的安全连接。这些证书不会更改,因此一旦获取证书,您可以将其硬编码到应用程序中。
这些证书包含公钥,不需要保密。您可以在源代码中使用它们,并在 GitHub 等公共平台上共享。
### 任务 - 设置 SSL 客户端
1. 如果尚未打开,请打开 `fruit-quality-detector` 应用项目。
1. 打开 `config.h` 头文件,并添加以下内容:
```cpp
const char *CERTIFICATE =
"-----BEGIN CERTIFICATE-----\r\n"
"MIIF8zCCBNugAwIBAgIQAueRcfuAIek/4tmDg0xQwDANBgkqhkiG9w0BAQwFADBh\r\n"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\r\n"
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\r\n"
"MjAeFw0yMDA3MjkxMjMwMDBaFw0yNDA2MjcyMzU5NTlaMFkxCzAJBgNVBAYTAlVT\r\n"
"MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jv\r\n"
"c29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwNjCCAiIwDQYJKoZIhvcNAQEBBQAD\r\n"
"ggIPADCCAgoCggIBALVGARl56bx3KBUSGuPc4H5uoNFkFH4e7pvTCxRi4j/+z+Xb\r\n"
"wjEz+5CipDOqjx9/jWjskL5dk7PaQkzItidsAAnDCW1leZBOIi68Lff1bjTeZgMY\r\n"
"iwdRd3Y39b/lcGpiuP2d23W95YHkMMT8IlWosYIX0f4kYb62rphyfnAjYb/4Od99\r\n"
"ThnhlAxGtfvSbXcBVIKCYfZgqRvV+5lReUnd1aNjRYVzPOoifgSx2fRyy1+pO1Uz\r\n"
"aMMNnIOE71bVYW0A1hr19w7kOb0KkJXoALTDDj1ukUEDqQuBfBxReL5mXiu1O7WG\r\n"
"0vltg0VZ/SZzctBsdBlx1BkmWYBW261KZgBivrql5ELTKKd8qgtHcLQA5fl6JB0Q\r\n"
"gs5XDaWehN86Gps5JW8ArjGtjcWAIP+X8CQaWfaCnuRm6Bk/03PQWhgdi84qwA0s\r\n"
"sRfFJwHUPTNSnE8EiGVk2frt0u8PG1pwSQsFuNJfcYIHEv1vOzP7uEOuDydsmCjh\r\n"
"lxuoK2n5/2aVR3BMTu+p4+gl8alXoBycyLmj3J/PUgqD8SL5fTCUegGsdia/Sa60\r\n"
"N2oV7vQ17wjMN+LXa2rjj/b4ZlZgXVojDmAjDwIRdDUujQu0RVsJqFLMzSIHpp2C\r\n"
"Zp7mIoLrySay2YYBu7SiNwL95X6He2kS8eefBBHjzwW/9FxGqry57i71c2cDAgMB\r\n"
"AAGjggGtMIIBqTAdBgNVHQ4EFgQU1cFnOsKjnfR3UltZEjgp5lVou6UwHwYDVR0j\r\n"
"BBgwFoAUTiJUIBiV5uNu5g/6+rkS7QYXjzkwDgYDVR0PAQH/BAQDAgGGMB0GA1Ud\r\n"
"JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMHYG\r\n"
"CCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu\r\n"
"Y29tMEAGCCsGAQUFBzAChjRodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln\r\n"
"aUNlcnRHbG9iYWxSb290RzIuY3J0MHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9j\r\n"
"cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOG\r\n"
"MWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5j\r\n"
"cmwwHQYDVR0gBBYwFDAIBgZngQwBAgEwCAYGZ4EMAQICMBAGCSsGAQQBgjcVAQQD\r\n"
"AgEAMA0GCSqGSIb3DQEBDAUAA4IBAQB2oWc93fB8esci/8esixj++N22meiGDjgF\r\n"
"+rA2LUK5IOQOgcUSTGKSqF9lYfAxPjrqPjDCUPHCURv+26ad5P/BYtXtbmtxJWu+\r\n"
"cS5BhMDPPeG3oPZwXRHBJFAkY4O4AF7RIAAUW6EzDflUoDHKv83zOiPfYGcpHc9s\r\n"
"kxAInCedk7QSgXvMARjjOqdakor21DTmNIUotxo8kHv5hwRlGhBJwps6fEVi1Bt0\r\n"
"trpM/3wYxlr473WSPUFZPgP1j519kLpWOJ8z09wxay+Br29irPcBYv0GMXlHqThy\r\n"
"8y4m/HyTQeI2IMvMrQnwqPpY+rLIXyviI2vLoI+4xKE4Rn38ZZ8m\r\n"
"-----END CERTIFICATE-----\r\n";
```
这是 *Microsoft Azure DigiCert Global Root G2 证书*——它是许多 Azure 服务全球使用的证书之一。
> 💁 要查看这是需要使用的证书,请在 macOS 或 Linux 上运行以下命令。如果您使用的是 Windows可以通过 [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/windows/wsl/?WT.mc_id=academic-17441-jabenn) 运行此命令:
>
> ```sh
> openssl s_client -showcerts -verify 5 -connect api.cognitive.microsoft.com:443
> ```
>
> 输出将列出 DigiCert Global Root G2 证书。
1. 打开 `main.cpp` 并添加以下 include 指令:
```cpp
#include <WiFiClientSecure.h>
```
1. 在 include 指令下方,声明一个 `WifiClientSecure` 实例:
```cpp
WiFiClientSecure client;
```
此类包含与 HTTPS 端点通信的代码。
1. 在 `connectWiFi` 方法中,将 WiFiClientSecure 设置为使用 DigiCert Global Root G2 证书:
```cpp
client.setCACert(CERTIFICATE);
```
### 任务 - 分类图像
1. 在 `platformio.ini` 文件的 `lib_deps` 列表中添加以下内容作为额外行:
```ini
bblanchon/ArduinoJson @ 6.17.3
```
这将导入 [ArduinoJson](https://arduinojson.org),一个 Arduino JSON 库,用于解码 REST API 的 JSON 响应。
1. 在 `config.h` 中,为 Custom Vision 服务的预测 URL 和 Key 添加常量:
```cpp
const char *PREDICTION_URL = "<PREDICTION_URL>";
const char *PREDICTION_KEY = "<PREDICTION_KEY>";
```
`<PREDICTION_URL>` 替换为 Custom Vision 的预测 URL。将 `<PREDICTION_KEY>` 替换为预测密钥。
1. 在 `main.cpp` 中,为 ArduinoJson 库添加 include 指令:
```cpp
#include <ArduinoJSON.h>
```
1. 在 `main.cpp` 中,添加以下函数,放置在 `buttonPressed` 函数上方:
```cpp
void classifyImage(byte *buffer, uint32_t length)
{
HTTPClient httpClient;
httpClient.begin(client, PREDICTION_URL);
httpClient.addHeader("Content-Type", "application/octet-stream");
httpClient.addHeader("Prediction-Key", PREDICTION_KEY);
int httpResponseCode = httpClient.POST(buffer, length);
if (httpResponseCode == 200)
{
String result = httpClient.getString();
DynamicJsonDocument doc(1024);
deserializeJson(doc, result.c_str());
JsonObject obj = doc.as<JsonObject>();
JsonArray predictions = obj["predictions"].as<JsonArray>();
for(JsonVariant prediction : predictions)
{
String tag = prediction["tagName"].as<String>();
float probability = prediction["probability"].as<float>();
char buff[32];
sprintf(buff, "%s:\t%.2f%%", tag.c_str(), probability * 100.0);
Serial.println(buff);
}
}
httpClient.end();
}
```
此代码首先声明一个 `HTTPClient`——一个包含与 REST API 交互方法的类。然后使用之前设置的 Azure 公钥,通过 `WiFiClientSecure` 实例连接客户端到预测 URL。
一旦连接成功,它会发送请求头——关于即将对 REST API 发出的请求的信息。`Content-Type` 请求头表示 API 调用将发送原始二进制数据,`Prediction-Key` 请求头传递 Custom Vision 的预测密钥。
接下来,向 HTTP 客户端发出 POST 请求,上传一个字节数组。该数组将包含从摄像头捕获的 JPEG 图像,当此函数被调用时上传。
> 💁 POST 请求用于发送数据并获取响应。还有其他请求类型,例如 GET 请求,用于检索数据。您的网页浏览器使用 GET 请求加载网页。
POST 请求返回一个响应状态码。这些是定义明确的值,其中 200 表示 **OK**——POST 请求成功。
> 💁 您可以在 [维基百科的 HTTP 状态码列表页面](https://wikipedia.org/wiki/List_of_HTTP_status_codes) 中查看所有响应状态码。
如果返回 200则从 HTTP 客户端读取结果。这是 REST API 的预测结果,以 JSON 文档的形式返回的文本响应。JSON 格式如下:
```jSON
{
"id":"45d614d3-7d6f-47e9-8fa2-04f237366a16",
"project":"135607e5-efac-4855-8afb-c93af3380531",
"iteration":"04f1c1fa-11ec-4e59-bb23-4c7aca353665",
"created":"2021-06-10T17:58:58.959Z",
"predictions":[
{
"probability":0.5582016,
"tagId":"05a432ea-9718-4098-b14f-5f0688149d64",
"tagName":"ripe"
},
{
"probability":0.44179836,
"tagId":"bb091037-16e5-418e-a9ea-31c6a2920f17",
"tagName":"unripe"
}
]
}
```
这里重要的部分是 `predictions` 数组。它包含预测结果,每个标签都有一个条目,包含标签名称和概率。返回的概率是 0-1 的浮点数,其中 0 表示与标签匹配的可能性为 0%1 表示与标签匹配的可能性为 100%。
> 💁 图像分类器会返回所有使用过的标签的百分比。每个标签都会有一个图像与该标签匹配的概率。
该 JSON 被解码后,每个标签的概率会发送到串行监视器。
1. 在 `buttonPressed` 函数中,将保存到 SD 卡的代码替换为对 `classifyImage` 的调用,或者在图像写入后添加调用,但 **在**删除缓冲区之前:
```cpp
classifyImage(buffer, length);
```
> 💁 如果您替换了保存到 SD 卡的代码,可以清理代码,删除 `setupSDCard``saveToSDCard` 函数。
1. 上传并运行代码。将摄像头对准一些水果并按下 C 按钮。您将在串行监视器中看到输出:
```output
Connecting to WiFi..
Connected!
Image captured
Image read to buffer with length 8200
ripe: 56.84%
unripe: 43.16%
```
您将能够看到拍摄的图像,以及这些值在 Custom Vision 的 **Predictions** 标签中。
![Custom Vision 中的香蕉预测结果:成熟 56.8%,未成熟 43.1%](../../../../../translated_images/zh-CN/custom-vision-banana-prediction.30cdff4e1d72db5d9a0be0193790a47c2b387da034e12dc1314dd57ca2131b59.png)
> 💁 您可以在 [code-classify/wio-terminal](../../../../../4-manufacturing/lessons/2-check-fruit-from-device/code-classify/wio-terminal) 文件夹中找到此代码。
😀 您的水果质量分类程序成功了!
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用本翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,615 @@
# 在边缘设备上运行水果检测器
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-17.bc333c3c35ba8e42cce666cfffa82b915f787f455bd94e006aea2b6f2722421a.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看大图。
这段视频概述了如何在物联网设备上运行图像分类器,这是本课的主题。
[![Azure IoT Edge 上的自定义视觉 AI](https://img.youtube.com/vi/_K5fqGLO8us/0.jpg)](https://www.youtube.com/watch?v=_K5fqGLO8us)
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/33)
## 简介
在上一课中,你使用图像分类器对成熟和未成熟的水果进行分类,将物联网设备摄像头捕获的图像通过互联网发送到云服务。这种方式需要时间、花费金钱,并且根据所使用的图像数据类型,可能会带来隐私问题。
在本课中你将学习如何在边缘设备上运行机器学习ML模型——即在运行于你自己网络上的物联网设备上而不是在云端。你将了解边缘计算相较于云计算的优缺点如何将 AI 模型部署到边缘设备,以及如何从物联网设备访问它。
本课内容包括:
* [边缘计算](../../../../../4-manufacturing/lessons/3-run-fruit-detector-edge)
* [Azure IoT Edge](../../../../../4-manufacturing/lessons/3-run-fruit-detector-edge)
* [注册一个 IoT Edge 设备](../../../../../4-manufacturing/lessons/3-run-fruit-detector-edge)
* [设置 IoT Edge 设备](../../../../../4-manufacturing/lessons/3-run-fruit-detector-edge)
* [导出你的模型](../../../../../4-manufacturing/lessons/3-run-fruit-detector-edge)
* [准备容器以进行部署](../../../../../4-manufacturing/lessons/3-run-fruit-detector-edge)
* [部署容器](../../../../../4-manufacturing/lessons/3-run-fruit-detector-edge)
* [使用你的 IoT Edge 设备](../../../../../4-manufacturing/lessons/3-run-fruit-detector-edge)
## 边缘计算
边缘计算是指尽可能靠近数据生成位置的地方处理物联网数据。与在云端处理不同,边缘计算将处理移至云的边缘——即你的内部网络。
![一个架构图,显示云端的互联网服务和本地网络上的物联网设备](../../../../../translated_images/zh-CN/cloud-without-edge.b4da641f6022c95ed6b91fde8b5323abd2f94e0d52073ad54172ae8f5dac90e9.png)
到目前为止的课程中,你的设备一直在收集数据并将其发送到云端进行分析,在云端运行无服务器函数或 AI 模型。
![一个架构图,显示本地网络上的物联网设备连接到边缘设备,这些边缘设备再连接到云端](../../../../../translated_images/zh-CN/cloud-with-edge.1e26462c62c126fe150bd15a5714ddf0be599f09bacbad08b85be02b76ea1ae1.png)
边缘计算将部分云服务从云端移到与物联网设备同一网络上的计算机上,仅在需要时与云端通信。例如,你可以在边缘设备上运行 AI 模型来分析水果的成熟度,仅将分析结果(如成熟水果与未成熟水果的数量)发送回云端。
✅ 思考一下你迄今为止构建的物联网应用程序。哪些部分可以移到边缘?
### 优点
边缘计算的优点包括:
1. **速度** - 边缘计算非常适合时间敏感型数据,因为操作在与设备相同的网络上完成,而不是通过互联网进行调用。这使得速度更快,因为内部网络的运行速度通常比互联网连接快得多,数据传输的距离也更短。
> 💁 尽管互联网连接使用光纤电缆,数据可以以光速传输,但数据在全球范围内传输到云服务提供商仍需要时间。例如,如果你从欧洲向美国的云服务发送数据,数据通过光纤电缆跨越大西洋至少需要 28 毫秒,这还不包括数据到达跨大西洋电缆的时间、电信号与光信号的转换时间以及从光纤电缆到云服务提供商的时间。
边缘计算还需要更少的网络流量,从而降低了因互联网连接带宽有限而导致数据传输变慢的风险。
1. **远程可访问性** - 边缘计算在连接有限或没有连接,或者连接成本过高时仍然有效。例如,在基础设施有限的人道主义灾区或发展中国家工作时。
1. **成本更低** - 在边缘设备上进行数据收集、存储、分析和触发操作可以减少云服务的使用,从而降低物联网应用程序的总体成本。近年来,专为边缘计算设计的设备(如 [NVIDIA 的 Jetson Nano](https://developer.nvidia.com/embedded/jetson-nano-developer-kit))的兴起,这些设备可以使用 GPU 硬件在成本低于 100 美元的设备上运行 AI 工作负载。
1. **隐私和安全性** - 使用边缘计算,数据保留在你的网络中,而不会上传到云端。这对于敏感和个人身份信息来说通常是更好的选择,尤其是因为数据在分析后不需要存储,从而大大降低了数据泄露的风险。例如,医疗数据和安全摄像头录像。
1. **处理不安全设备** - 如果你有已知存在安全漏洞的设备,不希望它们直接连接到你的网络或互联网,那么你可以将它们连接到一个单独的网络,并通过一个网关 IoT Edge 设备进行管理。这个边缘设备可以同时连接到更广泛的网络或互联网,并管理数据的双向流动。
1. **支持不兼容设备** - 如果你有无法连接到 IoT Hub 的设备,例如只能通过 HTTP 连接的设备或只能通过蓝牙连接的设备,你可以使用 IoT Edge 设备作为网关设备,将消息转发到 IoT Hub。
✅ 做一些研究:边缘计算还有哪些其他优点?
### 缺点
边缘计算也有一些缺点,在某些情况下云计算可能是更好的选择:
1. **规模和灵活性** - 云计算可以通过增加或减少服务器和其他资源实时调整网络和数据需求。而增加边缘计算机需要手动添加更多设备。
1. **可靠性和弹性** - 云计算通常提供多个服务器,甚至位于多个位置,以实现冗余和灾难恢复。而在边缘实现同样的冗余需要大量投资和配置工作。
1. **维护** - 云服务提供商负责系统维护和更新。
✅ 做一些研究:边缘计算还有哪些其他缺点?
这些缺点实际上是使用云计算的优点的反面——你需要自己构建和管理这些设备,而不是依赖云服务提供商的专业知识和规模。
某些风险可以通过边缘计算的特性得到缓解。例如,如果你在工厂中运行一个边缘设备来收集机器数据,你不需要考虑某些灾难恢复场景。如果工厂断电,那么生成数据的机器也会断电,因此不需要备用的边缘设备。
对于物联网系统,你通常需要云计算和边缘计算的结合,根据系统、客户和维护者的需求利用每种服务的优势。
## Azure IoT Edge
![Azure IoT Edge 的标志](../../../../../translated_images/zh-CN/azure-iot-edge-logo.c1c076749b5cba2e8755262fadc2f19ca1146b948d76990b1229199ac2292d79.png)
Azure IoT Edge 是一项服务,可以帮助你将工作负载从云端移到边缘。你可以将设备设置为边缘设备,并从云端向该边缘设备部署代码。这使得你可以混合使用云和边缘的功能。
> 🎓 *工作负载* 是指执行某种工作的任何服务,例如 AI 模型、应用程序或无服务器函数。
例如,你可以在云端训练一个图像分类器,然后从云端将其部署到边缘设备。你的物联网设备随后将图像发送到边缘设备进行分类,而不是通过互联网发送图像。如果需要部署模型的新版本,你可以在云端训练它,并使用 IoT Edge 将新版本更新到边缘设备。
> 🎓 部署到 IoT Edge 的软件称为 *模块*。默认情况下IoT Edge 运行与 IoT Hub 通信的模块,例如 `edgeAgent``edgeHub` 模块。当你部署图像分类器时,它会作为一个额外的模块部署。
IoT Edge 内置于 IoT Hub 中,因此你可以使用与管理物联网设备相同的服务来管理边缘设备,并具有相同的安全级别。
IoT Edge 从 *容器* 中运行代码——容器是独立的应用程序,运行时与计算机上的其他应用程序隔离。当你运行一个容器时,它就像在你的计算机中运行的一个独立计算机,拥有自己的软件、服务和应用程序。大多数情况下,容器无法访问计算机上的任何内容,除非你选择共享某些内容(例如文件夹)给容器。容器通过开放端口暴露服务,你可以连接到这些端口或将其暴露到网络。
![一个网络请求被重定向到容器](../../../../../translated_images/zh-CN/container-web-browser.4ee81dd4f0d8838ce622b2a0d600b6a4322b5d4fe43159facd87b7b34f84d66a.png)
例如,你可以在端口 80默认 HTTP 端口)上运行一个带有网站的容器,然后也在你的计算机上通过端口 80 暴露它。
✅ 做一些研究:阅读有关容器和 Docker 或 Moby 等服务的资料。
你可以使用 Custom Vision 下载图像分类器,并将其作为容器部署,无论是直接运行在设备上还是通过 IoT Edge 部署。一旦它们在容器中运行,你可以使用与云版本相同的 REST API 访问它们,但端点指向运行容器的边缘设备。
## 注册一个 IoT Edge 设备
要使用 IoT Edge 设备,必须在 IoT Hub 中注册它。
### 任务 - 注册一个 IoT Edge 设备
1. 在 `fruit-quality-detector` 资源组中创建一个 IoT Hub。为其命名一个基于 `fruit-quality-detector` 的唯一名称。
1. 在你的 IoT Hub 中注册一个名为 `fruit-quality-detector-edge` 的 IoT Edge 设备。注册命令与注册非边缘设备的命令类似,但需要传递 `--edge-enabled` 标志。
```sh
az iot hub device-identity create --edge-enabled \
--device-id fruit-quality-detector-edge \
--hub-name <hub_name>
```
`<hub_name>` 替换为你的 IoT Hub 的名称。
1. 使用以下命令获取设备的连接字符串:
```sh
az iot hub device-identity connection-string show --device-id fruit-quality-detector-edge \
--output table \
--hub-name <hub_name>
```
`<hub_name>` 替换为你的 IoT Hub 的名称。
复制输出中显示的连接字符串。
## 设置 IoT Edge 设备
在你的 IoT Hub 中创建了边缘设备注册后,你可以设置边缘设备。
### 任务 - 安装并启动 IoT Edge 运行时
**IoT Edge 运行时仅运行 Linux 容器。** 它可以运行在 Linux 上,或者通过 Linux 虚拟机运行在 Windows 上。
* 如果你使用 Raspberry Pi 作为物联网设备,那么它运行的是支持的 Linux 版本,可以托管 IoT Edge 运行时。按照 [Microsoft 文档中的安装 Azure IoT Edge for Linux 指南](https://docs.microsoft.com/azure/iot-edge/how-to-install-iot-edge?WT.mc_id=academic-17441-jabenn) 安装 IoT Edge 并设置连接字符串。
> 💁 请记住Raspberry Pi OS 是 Debian Linux 的一个变体。
* 如果你没有使用 Raspberry Pi但有一台 Linux 计算机,你可以运行 IoT Edge 运行时。按照 [Microsoft 文档中的安装 Azure IoT Edge for Linux 指南](https://docs.microsoft.com/azure/iot-edge/how-to-install-iot-edge?WT.mc_id=academic-17441-jabenn) 安装 IoT Edge 并设置连接字符串。
* 如果你使用的是 Windows可以通过 Linux 虚拟机安装 IoT Edge 运行时,按照 [Microsoft 文档中在 Windows 设备上部署第一个 IoT Edge 模块快速入门的安装和启动 IoT Edge 运行时部分](https://docs.microsoft.com/azure/iot-edge/quickstart?WT.mc_id=academic-17441-jabenn#install-and-start-the-iot-edge-runtime) 进行操作。到达 *部署模块* 部分时可以停止。
* 如果你使用的是 macOS可以在云中创建一个虚拟机VM作为你的 IoT Edge 设备。这些是你可以在云中创建并通过互联网访问的计算机。你可以创建一个安装了 IoT Edge 的 Linux 虚拟机。按照 [创建运行 IoT Edge 的虚拟机指南](vm-iotedge.md) 获取相关说明。
## 导出你的模型
要在边缘运行分类器,需要从 Custom Vision 导出它。Custom Vision 可以生成两种类型的模型——标准模型和紧凑型模型。紧凑型模型使用各种技术来减小模型的大小,使其足够小以便下载并部署到物联网设备上。
当你创建图像分类器时,使用的是 *Food* 域,这是一个针对食物图像训练优化的模型版本。在 Custom Vision 中你可以更改项目的域使用你的训练数据重新训练一个新域的模型。Custom Vision 支持的所有域都可以作为标准和紧凑型模型使用。
### 任务 - 使用 Food紧凑型域训练你的模型
1. 打开 [CustomVision.ai](https://customvision.ai) 的 Custom Vision 门户并登录(如果尚未打开)。然后打开你的 `fruit-quality-detector` 项目。
1. 点击 **设置** 按钮(⚙ 图标)。
1. 在 *Domains* 列表中,选择 *Food (compact)*
1. 在 *Export Capabilities* 下,确保选择了 *Basic platforms (Tensorflow, CoreML, ONNX, ...)*
1. 在设置页面底部,点击 **保存更改**。
1. 使用 **训练** 按钮重新训练模型,选择 *快速训练*
### 任务 - 导出你的模型
模型训练完成后,需要将其导出为一个容器。
1. 点击 **性能** 标签,找到使用紧凑域训练的最新迭代。
1. 点击顶部的 **导出** 按钮。
1. 选择 **DockerFile**,然后选择与你的边缘设备匹配的版本:
* 如果你在 Linux 电脑、Windows 电脑或虚拟机上运行 IoT Edge选择 *Linux* 版本。
* 如果你在 Raspberry Pi 上运行 IoT Edge选择 *ARM (Raspberry Pi 3)* 版本。
> 🎓 Docker 是管理容器最流行的工具之一,而 DockerFile 是一组关于如何设置容器的指令。
1. 点击 **导出** 让 Custom Vision 创建相关文件,然后点击 **下载** 将它们下载为一个 zip 文件。
1. 将文件保存到你的电脑,然后解压文件夹。
## 为部署准备容器
![容器被构建后推送到容器注册表,然后通过 IoT Edge 从容器注册表部署到边缘设备](../../../../../translated_images/zh-CN/container-edge-flow.c246050dd60ceefdb6ace026a4ce5c6aa4112bb5898ae23fbb2ab4be29ae3e1b.png)
下载模型后需要将其构建为容器然后推送到容器注册表——一个在线存储容器的位置。IoT Edge 可以从注册表下载容器并将其推送到你的设备。
![Azure 容器注册表标志](../../../../../translated_images/zh-CN/azure-container-registry-logo.09494206991d4b295025ebff7d4e2900325e527a59184ffbc8464b6ab59654be.png)
本课程中使用的容器注册表是 Azure 容器注册表。这不是一个免费服务,因此为了节省费用,请确保在完成后[清理你的项目](../../../clean-up.md)。
> 💁 你可以在 [Azure 容器注册表定价页面](https://azure.microsoft.com/pricing/details/container-registry/?WT.mc_id=academic-17441-jabenn) 查看使用 Azure 容器注册表的费用。
### 任务 - 安装 Docker
为了构建和部署分类器,你可能需要安装 [Docker](https://www.docker.com/)。
只有当你计划在与安装 IoT Edge 的设备不同的设备上构建容器时才需要安装 Docker——安装 IoT Edge 的过程中会自动安装 Docker。
1. 如果你在与 IoT Edge 设备不同的设备上构建 Docker 容器,请按照 [Docker 安装页面](https://www.docker.com/products/docker-desktop) 上的安装说明安装 Docker Desktop 或 Docker 引擎。确保安装后运行。
### 任务 - 创建容器注册表资源
1. 在终端或命令提示符中运行以下命令以创建 Azure 容器注册表资源:
```sh
az acr create --resource-group fruit-quality-detector \
--sku Basic \
--name <Container registry name>
```
`<Container registry name>` 替换为容器注册表的唯一名称,仅使用字母和数字。可以基于 `fruitqualitydetector` 创建名称。此名称将成为访问容器注册表的 URL 的一部分,因此需要全局唯一。
1. 使用以下命令登录到 Azure 容器注册表:
```sh
az acr login --name <Container registry name>
```
`<Container registry name>` 替换为你为容器注册表使用的名称。
1. 使用以下命令将容器注册表设置为管理员模式,以便生成密码:
```sh
az acr update --admin-enabled true \
--name <Container registry name>
```
`<Container registry name>` 替换为你为容器注册表使用的名称。
1. 使用以下命令生成容器注册表的密码:
```sh
az acr credential renew --password-name password \
--output table \
--name <Container registry name>
```
`<Container registry name>` 替换为你为容器注册表使用的名称。
复制 `PASSWORD` 的值,因为稍后会用到。
### 任务 - 构建你的容器
从 Custom Vision 下载的是一个 DockerFile包含关于如何构建容器的指令以及将在容器内运行的应用程序代码用于托管你的 Custom Vision 模型和一个 REST API 来调用它。你可以使用 Docker 从 DockerFile 构建一个带标签的容器,然后将其推送到容器注册表。
> 🎓 容器会被赋予一个标签,定义其名称和版本。当需要更新容器时,可以使用相同的标签但更新版本。
1. 打开终端或命令提示符,导航到从 Custom Vision 下载的解压模型。
1. 运行以下命令以构建并标记镜像:
```sh
docker build --platform <platform> -t <Container registry name>.azurecr.io/classifier:v1 .
```
`<platform>` 替换为容器运行的平台。如果你在 Raspberry Pi 上运行 IoT Edge将其设置为 `linux/armhf`,否则设置为 `linux/amd64`
> 💁 如果你在运行 IoT Edge 的设备上运行此命令,例如在 Raspberry Pi 上运行,可以省略 `--platform <platform>` 部分,因为它会默认使用当前平台。
`<Container registry name>` 替换为你为容器注册表使用的名称。
> 💁 如果你在 Linux 或 Raspberry Pi OS 上运行此命令,可能需要使用 `sudo`
Docker 将构建镜像,配置所需的软件。镜像将被标记为 `classifier:v1`
```output
➜ d4ccc45da0bb478bad287128e1274c3c.DockerFile.Linux docker build --platform linux/amd64 -t fruitqualitydetectorjimb.azurecr.io/classifier:v1 .
[+] Building 102.4s (11/11) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 131B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/python:3.7-slim
=> [internal] load build context
=> => transferring context: 905B
=> [1/6] FROM docker.io/library/python:3.7-slim@sha256:b21b91c9618e951a8cbca5b696424fa5e820800a88b7e7afd66bba0441a764d6
=> => resolve docker.io/library/python:3.7-slim@sha256:b21b91c9618e951a8cbca5b696424fa5e820800a88b7e7afd66bba0441a764d6
=> => sha256:b4d181a07f8025e00e0cb28f1cc14613da2ce26450b80c54aea537fa93cf3bda 27.15MB / 27.15MB
=> => sha256:de8ecf497b753094723ccf9cea8a46076e7cb845f333df99a6f4f397c93c6ea9 2.77MB / 2.77MB
=> => sha256:707b80804672b7c5d8f21e37c8396f319151e1298d976186b4f3b76ead9f10c8 10.06MB / 10.06MB
=> => sha256:b21b91c9618e951a8cbca5b696424fa5e820800a88b7e7afd66bba0441a764d6 1.86kB / 1.86kB
=> => sha256:44073386687709c437586676b572ff45128ff1f1570153c2f727140d4a9accad 1.37kB / 1.37kB
=> => sha256:3d94f0f2ca798607808b771a7766f47ae62a26f820e871dd488baeccc69838d1 8.31kB / 8.31kB
=> => sha256:283715715396fd56d0e90355125fd4ec57b4f0773f306fcd5fa353b998beeb41 233B / 233B
=> => sha256:8353afd48f6b84c3603ea49d204bdcf2a1daada15f5d6cad9cc916e186610a9f 2.64MB / 2.64MB
=> => extracting sha256:b4d181a07f8025e00e0cb28f1cc14613da2ce26450b80c54aea537fa93cf3bda
=> => extracting sha256:de8ecf497b753094723ccf9cea8a46076e7cb845f333df99a6f4f397c93c6ea9
=> => extracting sha256:707b80804672b7c5d8f21e37c8396f319151e1298d976186b4f3b76ead9f10c8
=> => extracting sha256:283715715396fd56d0e90355125fd4ec57b4f0773f306fcd5fa353b998beeb41
=> => extracting sha256:8353afd48f6b84c3603ea49d204bdcf2a1daada15f5d6cad9cc916e186610a9f
=> [2/6] RUN pip install -U pip
=> [3/6] RUN pip install --no-cache-dir numpy~=1.17.5 tensorflow~=2.0.2 flask~=1.1.2 pillow~=7.2.0
=> [4/6] RUN pip install --no-cache-dir mscviplib==2.200731.16
=> [5/6] COPY app /app
=> [6/6] WORKDIR /app
=> exporting to image
=> => exporting layers
=> => writing image sha256:1846b6f134431f78507ba7c079358ed66d944c0e185ab53428276bd822400386
=> => naming to fruitqualitydetectorjimb.azurecr.io/classifier:v1
```
### 任务 - 将容器推送到容器注册表
1. 使用以下命令将容器推送到容器注册表:
```sh
docker push <Container registry name>.azurecr.io/classifier:v1
```
`<Container registry name>` 替换为你为容器注册表使用的名称。
> 💁 如果你在 Linux 上运行,可能需要使用 `sudo`
容器将被推送到容器注册表。
```output
➜ d4ccc45da0bb478bad287128e1274c3c.DockerFile.Linux docker push fruitqualitydetectorjimb.azurecr.io/classifier:v1
The push refers to repository [fruitqualitydetectorjimb.azurecr.io/classifier]
5f70bf18a086: Pushed
8a1ba9294a22: Pushed
56cf27184a76: Pushed
b32154f3f5dd: Pushed
36103e9a3104: Pushed
e2abb3cacca0: Pushed
4213fd357bbe: Pushed
7ea163ba4dce: Pushed
537313a13d90: Pushed
764055ebc9a7: Pushed
v1: digest: sha256:ea7894652e610de83a5a9e429618e763b8904284253f4fa0c9f65f0df3a5ded8 size: 2423
```
1. 为验证推送,可以使用以下命令列出注册表中的容器:
```sh
az acr repository list --output table \
--name <Container registry name>
```
`<Container registry name>` 替换为你为容器注册表使用的名称。
```output
➜ d4ccc45da0bb478bad287128e1274c3c.DockerFile.Linux az acr repository list --name fruitqualitydetectorjimb --output table
Result
----------
classifier
```
你会在输出中看到你的分类器。
## 部署你的容器
现在可以将容器部署到 IoT Edge 设备。要部署,需要定义一个部署清单——一个列出将部署到边缘设备的模块的 JSON 文档。
### 任务 - 创建部署清单
1. 在电脑上创建一个名为 `deployment.json` 的新文件。
1. 在文件中添加以下内容:
```json
{
"content": {
"modulesContent": {
"$edgeAgent": {
"properties.desired": {
"schemaVersion": "1.1",
"runtime": {
"type": "docker",
"settings": {
"minDockerVersion": "v1.25",
"loggingOptions": "",
"registryCredentials": {
"ClassifierRegistry": {
"username": "<Container registry name>",
"password": "<Container registry password>",
"address": "<Container registry name>.azurecr.io"
}
}
}
},
"systemModules": {
"edgeAgent": {
"type": "docker",
"settings": {
"image": "mcr.microsoft.com/azureiotedge-agent:1.1",
"createOptions": "{}"
}
},
"edgeHub": {
"type": "docker",
"status": "running",
"restartPolicy": "always",
"settings": {
"image": "mcr.microsoft.com/azureiotedge-hub:1.1",
"createOptions": "{\"HostConfig\":{\"PortBindings\":{\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}"
}
}
},
"modules": {
"ImageClassifier": {
"version": "1.0",
"type": "docker",
"status": "running",
"restartPolicy": "always",
"settings": {
"image": "<Container registry name>.azurecr.io/classifier:v1",
"createOptions": "{\"ExposedPorts\": {\"80/tcp\": {}},\"HostConfig\": {\"PortBindings\": {\"80/tcp\": [{\"HostPort\": \"80\"}]}}}"
}
}
}
}
},
"$edgeHub": {
"properties.desired": {
"schemaVersion": "1.1",
"routes": {
"upstream": "FROM /messages/* INTO $upstream"
},
"storeAndForwardConfiguration": {
"timeToLiveSecs": 7200
}
}
}
}
}
}
```
> 💁 你可以在 [code-deployment/deployment](../../../../../4-manufacturing/lessons/3-run-fruit-detector-edge/code-deployment/deployment) 文件夹中找到此文件。
将文件中的三个 `<Container registry name>` 替换为你为容器注册表使用的名称。一个在 `ImageClassifier` 模块部分,另外两个在 `registryCredentials` 部分。
`registryCredentials` 部分中的 `<Container registry password>` 替换为你的容器注册表密码。
1. 从包含部署清单的文件夹运行以下命令:
```sh
az iot edge set-modules --device-id fruit-quality-detector-edge \
--content deployment.json \
--hub-name <hub_name>
```
`<hub_name>` 替换为你的 IoT Hub 名称。
图像分类器模块将被部署到你的边缘设备。
### 任务 - 验证分类器是否运行
1. 连接到 IoT Edge 设备:
* 如果你使用 Raspberry Pi 运行 IoT Edge可以通过终端使用 ssh 或通过 VS Code 的远程 SSH 会话连接。
* 如果你在 Windows 上的 Linux 容器中运行 IoT Edge请按照 [验证成功配置指南](https://docs.microsoft.com/azure/iot-edge/how-to-install-iot-edge-on-windows?WT.mc_id=academic-17441-jabenn&view=iotedge-2018-06&tabs=powershell#verify-successful-configuration) 的步骤连接到 IoT Edge 设备。
* 如果你在虚拟机上运行 IoT Edge可以使用创建虚拟机时设置的 `adminUsername``password` 通过 IP 地址或 DNS 名称 SSH 到机器:
```sh
ssh <adminUsername>@<IP address>
```
或:
```sh
ssh <adminUsername>@<DNS Name>
```
按提示输入密码。
1. 连接后,运行以下命令获取 IoT Edge 模块列表:
```sh
iotedge list
```
> 💁 你可能需要使用 `sudo` 运行此命令。
你会看到运行中的模块:
```output
jim@fruit-quality-detector-jimb:~$ iotedge list
NAME STATUS DESCRIPTION CONFIG
ImageClassifier running Up 42 minutes fruitqualitydetectorjimb.azurecr.io/classifier:v1
edgeAgent running Up 42 minutes mcr.microsoft.com/azureiotedge-agent:1.1
edgeHub running Up 42 minutes mcr.microsoft.com/azureiotedge-hub:1.1
```
1. 使用以下命令检查图像分类器模块的日志:
```sh
iotedge logs ImageClassifier
```
> 💁 你可能需要使用 `sudo` 运行此命令。
```output
jim@fruit-quality-detector-jimb:~$ iotedge logs ImageClassifier
2021-07-05 20:30:15.387144: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2021-07-05 20:30:15.392185: I tensorflow/core/platform/profile_utils/cpu_utils.cc:94] CPU Frequency: 2394450000 Hz
2021-07-05 20:30:15.392712: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x55ed9ac83470 executing computations on platform Host. Devices:
2021-07-05 20:30:15.392806: I tensorflow/compiler/xla/service/service.cc:175] StreamExecutor device (0): Host, Default Version
Loading model...Success!
Loading labels...2 found. Success!
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
```
### 任务 - 测试图像分类器
1. 你可以使用 CURL 测试图像分类器,使用运行 IoT Edge 代理的电脑的 IP 地址或主机名。找到 IP 地址:
* 如果你在运行 IoT Edge 的同一台机器上,可以使用 `localhost` 作为主机名。
* 如果你使用虚拟机,可以使用虚拟机的 IP 地址或 DNS 名称。
* 否则可以获取运行 IoT Edge 的机器的 IP 地址:
* 在 Windows 10 上,按照 [查找 IP 地址指南](https://support.microsoft.com/windows/find-your-ip-address-f21a9bbc-c582-55cd-35e0-73431160a1b9?WT.mc_id=academic-17441-jabenn)。
* 在 macOS 上,按照 [如何在 Mac 上查找 IP 地址指南](https://www.hellotech.com/guide/for/how-to-find-ip-address-on-mac)。
* 在 Linux 上,按照 [如何在 Linux 中查找 IP 地址指南](https://opensource.com/article/18/5/how-find-ip-address-linux) 中的查找私有 IP 地址部分。
1. 你可以使用本地文件运行以下 CURL 命令测试容器:
```sh
curl --location \
--request POST 'http://<IP address or name>/image' \
--header 'Content-Type: image/png' \
--data-binary '@<file_Name>'
```
`<IP address or name>` 替换为运行 IoT Edge 的电脑的 IP 地址或主机名。将 `<file_Name>` 替换为要测试的文件名。
你会在输出中看到预测结果:
```output
{
"created": "2021-07-05T21:44:39.573181",
"id": "",
"iteration": "",
"predictions": [
{
"boundingBox": null,
"probability": 0.9995615482330322,
"tagId": "",
"tagName": "ripe"
},
{
"boundingBox": null,
"probability": 0.0004384400090202689,
"tagId": "",
"tagName": "unripe"
}
],
"project": ""
}
```
> 💁 这里不需要提供预测密钥,因为这不是使用 Azure 资源。相反,安全性会基于内部网络的内部安全需求进行配置,而不是依赖公共端点和 API 密钥。
## 使用你的 IoT Edge 设备
现在你的图像分类器已经部署到 IoT Edge 设备,可以从 IoT 设备使用它。
### 任务 - 使用你的 IoT Edge 设备
按照相关指南使用 IoT Edge 分类器对图像进行分类:
* [Arduino - Wio Terminal](wio-terminal.md)
* [单板计算机 - Raspberry Pi/虚拟 IoT 设备](single-board-computer.md)
### 模型重新训练
在 IoT Edge 上运行图像分类器的一个缺点是它们没有连接到你的 Custom Vision 项目。如果你查看 Custom Vision 中的 **预测** 标签,你不会看到使用基于 Edge 的分类器分类的图像。
这是预期行为——图像不会被发送到云端进行分类,因此它们不会在云端可用。在 IoT Edge 上使用的一个优点是隐私,确保图像不会离开你的网络,另一个优点是可以离线工作,因此不依赖于设备没有互联网连接时上传图像。缺点是改进你的模型——你需要实现另一种存储图像的方法,这些图像可以手动重新分类以改进和重新训练图像分类器。
✅ 思考如何上传图像以重新训练分类器。
---
## 🚀 挑战
在边缘设备上运行 AI 模型可能比在云端更快——网络跳跃更短。也可能更慢,因为运行模型的硬件可能没有云端强大。
进行一些计时比较,看看调用边缘设备是否比调用云端更快或更慢?思考解释差异或没有差异的原因。研究如何使用专用硬件在边缘更快地运行 AI 模型。
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/34)
## 复习与自学
* 阅读更多关于容器的信息:[Wikipedia 上的操作系统级虚拟化页面](https://wikipedia.org/wiki/OS-level_virtualization)
* 阅读更多关于边缘计算的内容重点了解5G如何帮助扩展边缘计算请参阅[NetworkWorld上的“什么是边缘计算及其重要性”文章](https://www.networkworld.com/article/3224893/what-is-edge-computing-and-how-its-changing-the-network.html)
* 通过观看[Microsoft Channel9上的Learn Live节目“学习如何在边缘上的预构建AI服务中使用Azure IoT Edge进行语言检测”](https://channel9.msdn.com/Shows/Learn-Live/Sharpen-Your-AI-Edge-Skills-Episode-4-Learn-How-to-Use-Azure-IoT-Edge-on-a-Pre-Built-AI-Service-on-t?WT.mc_id=academic-17441-jabenn)了解更多关于在IoT Edge上运行AI服务的信息
## 作业
[在边缘运行其他服务](assignment.md)
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,16 @@
# 在边缘运行其他服务
## 说明
不仅仅是图像分类器可以在边缘运行,任何可以打包成容器的内容都可以部署到 IoT Edge 设备上。无服务器代码(如你在之前课程中创建的触发器)可以作为 Azure Functions 运行在容器中,因此也可以运行在 IoT Edge 上。
选择之前的某一课程,尝试将 Azure Functions 应用程序运行在 IoT Edge 容器中。你可以在 [教程:将 Azure Functions 部署为 IoT Edge 模块 - Microsoft 文档](https://docs.microsoft.com/azure/iot-edge/tutorial-deploy-function?WT.mc_id=academic-17441-jabenn&view=iotedge-2020-11) 中找到一个使用不同 Functions 应用项目的指南。
## 评分标准
| 标准 | 卓越 | 合格 | 需要改进 |
| ---- | ---- | ---- | -------- |
| 将 Azure Functions 应用部署到 IoT Edge | 成功将 Azure Functions 应用部署到 IoT Edge并使用 IoT 设备运行 IoT 数据触发器 | 成功将 Functions 应用部署到 IoT Edge但未能触发触发器 | 未能将 Functions 应用部署到 IoT Edge |
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于重要信息,建议使用专业人工翻译。我们对因使用此翻译而产生的任何误解或误读不承担责任。

@ -0,0 +1,57 @@
# 使用基于 IoT Edge 的图像分类器对图像进行分类 - 虚拟 IoT 硬件和 Raspberry Pi
在本课的这一部分中,您将使用运行在 IoT Edge 设备上的图像分类器。
## 使用 IoT Edge 分类器
IoT 设备可以被重新配置为使用 IoT Edge 图像分类器。图像分类器的 URL 是 `http://<IP address or name>/image`,将 `<IP address or name>` 替换为运行 IoT Edge 的计算机的 IP 地址或主机名。
Custom Vision 的 Python 库仅支持云托管的模型,而不支持托管在 IoT Edge 上的模型。这意味着您需要使用 REST API 来调用分类器。
### 任务 - 使用 IoT Edge 分类器
1. 如果尚未打开,请在 VS Code 中打开 `fruit-quality-detector` 项目。如果您使用的是虚拟 IoT 设备,请确保虚拟环境已激活。
1. 打开 `app.py` 文件,删除从 `azure.cognitiveservices.vision.customvision.prediction``msrest.authentication` 导入的语句。
1. 在文件顶部添加以下导入:
```python
import requests
```
1. 删除保存图像到文件后从 `image_file.write(image.read())` 到文件末尾的所有代码。
1. 在文件末尾添加以下代码:
```python
prediction_url = '<URL>'
headers = {
'Content-Type' : 'application/octet-stream'
}
image.seek(0)
response = requests.post(prediction_url, headers=headers, data=image)
results = response.json()
for prediction in results['predictions']:
print(f'{prediction["tagName"]}:\t{prediction["probability"] * 100:.2f}%')
```
`<URL>` 替换为您的分类器的 URL。
这段代码通过 REST POST 请求向分类器发送图像作为请求的主体。结果以 JSON 格式返回,并被解码以打印出概率。
1. 运行您的代码,使用摄像头对准一些水果,或者使用适当的图像集,或者如果使用虚拟 IoT 硬件,则确保水果在您的网络摄像头中可见。您将在控制台中看到输出:
```output
(.venv) ➜ fruit-quality-detector python app.py
ripe: 56.84%
unripe: 43.16%
```
> 💁 您可以在 [code-classify/pi](../../../../../4-manufacturing/lessons/3-run-fruit-detector-edge/code-classify/pi) 或 [code-classify/virtual-iot-device](../../../../../4-manufacturing/lessons/3-run-fruit-detector-edge/code-classify/virtual-iot-device) 文件夹中找到这段代码。
😀 您的水果质量分类器程序运行成功!
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,104 @@
# 创建运行 IoT Edge 的虚拟机
在 Azure 中,你可以创建一台虚拟机——一台云端的计算机,你可以根据自己的需求进行配置并运行自己的软件。
> 💁 你可以在 [维基百科的虚拟机页面](https://wikipedia.org/wiki/Virtual_machine) 上了解更多关于虚拟机的信息。
## 任务 - 设置 IoT Edge 虚拟机
1. 运行以下命令来创建一台已经预装了 Azure IoT Edge 的虚拟机:
```sh
az deployment group create \
--resource-group fruit-quality-detector \
--template-uri https://raw.githubusercontent.com/Azure/iotedge-vm-deploy/1.2.0/edgeDeploy.json \
--parameters dnsLabelPrefix=<vm_name> \
--parameters adminUsername=<username> \
--parameters deviceConnectionString="<connection_string>" \
--parameters authenticationType=password \
--parameters adminPasswordOrKey="<password>"
```
`<vm_name>` 替换为这台虚拟机的名称。这个名称需要在全球范围内唯一,因此可以使用类似 `fruit-quality-detector-vm-` 的名称,并在后面加上你的名字或其他值。
`<username>``<password>` 替换为用于登录虚拟机的用户名和密码。这些需要相对安全,因此不能使用类似 admin/password 的简单组合。
`<connection_string>` 替换为 `fruit-quality-detector-edge` IoT Edge 设备的连接字符串。
这将创建一台配置为 `DS1 v2` 的虚拟机。这些类别表示机器的性能,也决定了其成本。这台虚拟机有 1 个 CPU 和 3.5GB 的内存。
> 💰 你可以在 [Azure 虚拟机定价指南](https://azure.microsoft.com/pricing/details/virtual-machines/linux/?WT.mc_id=academic-17441-jabenn) 上查看这些虚拟机的当前定价。
一旦虚拟机创建完成IoT Edge 运行时将自动安装,并配置为连接到你的 IoT Hub作为你的 `fruit-quality-detector-edge` 设备。
1. 你需要虚拟机的 IP 地址或 DNS 名称来从中调用图像分类器。运行以下命令获取这些信息:
```sh
az vm list --resource-group fruit-quality-detector \
--output table \
--show-details
```
复制 `PublicIps` 字段或 `Fqdns` 字段的值。
1. 虚拟机是需要花费费用的。截至撰写本文时,一台 DS1 虚拟机的费用大约是每小时 $0.06。为了节省成本,你应该在不使用虚拟机时将其关闭,并在完成项目后删除它。
你可以配置虚拟机在每天的某个时间自动关闭。如果你忘记手动关闭,它将确保你不会被计费超过自动关闭的时间。使用以下命令设置自动关闭:
```sh
az vm auto-shutdown --resource-group fruit-quality-detector \
--name <vm_name> \
--time <shutdown_time_utc>
```
`<vm_name>` 替换为你的虚拟机名称。
`<shutdown_time_utc>` 替换为你希望虚拟机关闭的 UTC 时间,使用 4 位数字表示为 HHMM。例如如果你希望在 UTC 时间午夜关闭,可以设置为 `0000`。如果是美国西海岸的晚上 7:30则使用 `0230`(美国西海岸的晚上 7:30 是 UTC 时间的凌晨 2:30
1. 你的图像分类器将在这个边缘设备上运行,监听 80 端口(标准 HTTP 端口)。默认情况下,虚拟机的入站端口是被阻止的,因此你需要启用 80 端口。端口是在网络安全组上启用的,因此首先需要知道虚拟机的网络安全组名称,可以通过以下命令找到:
```sh
az network nsg list --resource-group fruit-quality-detector \
--output table
```
复制 `Name` 字段的值。
1. 运行以下命令,为网络安全组添加一条规则以打开 80 端口:
```sh
az network nsg rule create \
--resource-group fruit-quality-detector \
--name Port_80 \
--protocol tcp \
--priority 1010 \
--destination-port-range 80 \
--nsg-name <nsg name>
```
`<nsg name>` 替换为上一步中获取的网络安全组名称。
### 任务 - 管理虚拟机以降低成本
1. 当你不使用虚拟机时,应该将其关闭。使用以下命令关闭虚拟机:
```sh
az vm deallocate --resource-group fruit-quality-detector \
--name <vm_name>
```
`<vm_name>` 替换为你的虚拟机名称。
> 💁 还有一个 `az vm stop` 命令可以停止虚拟机,但它会保留计算资源分配给你,因此你仍然需要支付与运行状态相同的费用。
1. 要重新启动虚拟机,使用以下命令:
```sh
az vm start --resource-group fruit-quality-detector \
--name <vm_name>
```
`<vm_name>` 替换为你的虚拟机名称。
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。因使用本翻译而引起的任何误解或误读,我们概不负责。

@ -0,0 +1,55 @@
# 使用基于 IoT Edge 的图像分类器对图像进行分类 - Wio Terminal
在本节课程中,您将使用运行在 IoT Edge 设备上的图像分类器。
## 使用 IoT Edge 分类器
IoT 设备可以被重新定向以使用 IoT Edge 图像分类器。图像分类器的 URL 是 `http://<IP address or name>/image`,将 `<IP address or name>` 替换为运行 IoT Edge 的计算机的 IP 地址或主机名。
### 任务 - 使用 IoT Edge 分类器
1. 如果尚未打开 `fruit-quality-detector` 应用项目,请将其打开。
1. 图像分类器作为一个使用 HTTP 的 REST API 运行,而不是 HTTPS因此调用需要使用仅支持 HTTP 调用的 WiFi 客户端。这意味着不需要证书。从 `config.h` 文件中删除 `CERTIFICATE`
1. 需要将 `config.h` 文件中的预测 URL 更新为新 URL。您还可以删除 `PREDICTION_KEY`,因为它不再需要。
```cpp
const char *PREDICTION_URL = "<URL>";
```
`<URL>` 替换为您的分类器的 URL。
1. 在 `main.cpp` 中,将 WiFi Client Secure 的 include 指令更改为导入标准 HTTP 版本:
```cpp
#include <WiFiClient.h>
```
1. 将 `WiFiClient` 的声明更改为 HTTP 版本:
```cpp
WiFiClient client;
```
1. 找到设置 WiFi 客户端证书的那一行。从 `connectWiFi` 函数中删除 `client.setCACert(CERTIFICATE);` 这一行。
1. 在 `classifyImage` 函数中,删除设置预测密钥的这一行:`httpClient.addHeader("Prediction-Key", PREDICTION_KEY);`。
1. 上传并运行您的代码。将摄像头对准一些水果并按下 C 按钮。您将在串行监视器中看到输出:
```output
Connecting to WiFi..
Connected!
Image captured
Image read to buffer with length 8200
ripe: 56.84%
unripe: 43.16%
```
> 💁 您可以在 [code-classify/wio-terminal](../../../../../4-manufacturing/lessons/3-run-fruit-detector-edge/code-classify/wio-terminal) 文件夹中找到此代码。
😀 您的水果质量分类器程序运行成功!
**免责声明**
本文档使用AI翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用此翻译而引起的任何误解或误读不承担责任。

@ -0,0 +1,222 @@
# 从传感器触发水果质量检测
![本课的手绘笔记概览](../../../../../translated_images/zh-CN/lesson-18.92c32ed1d354caa5a54baa4032cf0b172d4655e8e326ad5d46c558a0def15365.jpg)
> 手绘笔记由 [Nitya Narasimhan](https://github.com/nitya) 提供。点击图片查看更大版本。
## 课前测验
[课前测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/35)
## 简介
一个物联网应用不仅仅是单个设备捕获数据并发送到云端,通常是多个设备协同工作,通过传感器从物理世界捕获数据,根据这些数据做出决策,并通过执行器或可视化与物理世界进行交互。
在本课中,您将学习如何设计复杂的物联网应用,包括整合多个传感器、多个云服务来分析和存储数据,以及通过执行器展示响应。您将学习如何设计一个水果质量控制系统原型,包括使用接近传感器触发物联网应用,以及该原型的架构。
本课内容包括:
* [设计复杂的物联网应用](../../../../../4-manufacturing/lessons/4-trigger-fruit-detector)
* [设计水果质量控制系统](../../../../../4-manufacturing/lessons/4-trigger-fruit-detector)
* [从传感器触发水果质量检测](../../../../../4-manufacturing/lessons/4-trigger-fruit-detector)
* [水果质量检测器使用的数据](../../../../../4-manufacturing/lessons/4-trigger-fruit-detector)
* [使用开发设备模拟多个物联网设备](../../../../../4-manufacturing/lessons/4-trigger-fruit-detector)
* [进入生产阶段](../../../../../4-manufacturing/lessons/4-trigger-fruit-detector)
> 🗑 这是本项目的最后一课,因此在完成本课和作业后,请记得清理您的云服务。您需要这些服务来完成作业,因此请确保先完成作业。
>
> 如果需要,请参考 [清理项目指南](../../../clean-up.md) 了解如何操作。
## 设计复杂的物联网应用
物联网应用由许多组件组成。这包括各种设备和互联网服务。
物联网应用可以描述为 *设备* 发送数据生成 *洞察*,这些 *洞察* 生成 *行动* 来改善业务或流程。例如,一个引擎(设备)发送温度数据。这些数据用于评估引擎是否表现正常(洞察)。洞察用于主动优先安排引擎的维护计划(行动)。
* 不同的设备收集不同的数据。
* 物联网服务对这些数据进行分析,有时还会结合其他来源的数据。
* 这些洞察驱动行动,包括控制设备中的执行器或可视化数据。
### 参考物联网架构
![参考物联网架构](../../../../../translated_images/zh-CN/iot-reference-architecture.2278b98b55c6d4e89bde18eada3688d893861d43507641804dd2f9d3079cfaa0.png)
上图展示了一个参考物联网架构。
> 🎓 *参考架构* 是一个示例架构,您可以在设计新系统时作为参考。在这种情况下,如果您正在构建一个新的物联网系统,可以遵循参考架构,并根据需要替换自己的设备和服务。
* **设备** 是通过传感器收集数据的设备,可能与边缘服务交互以解释数据,例如使用图像分类器解释图像数据。设备的数据被发送到物联网服务。
* **洞察** 来自无服务器应用程序,或存储数据上的分析。
* **行动** 可以是发送给设备的命令,或数据的可视化以帮助人类做出决策。
![参考物联网架构](../../../../../translated_images/zh-CN/iot-reference-architecture-azure.0b8d2161af924cb18ae48a8558a19541cca47f27264851b5b7e56d7b8bb372ac.png)
上图展示了这些课程中涉及的一些组件和服务,以及它们如何在参考物联网架构中链接在一起。
* **设备** - 您已经编写了设备代码来捕获传感器数据,并使用 Custom Vision 在云端和边缘设备上分析图像。这些数据被发送到 IoT Hub。
* **洞察** - 您已经使用 Azure Functions 响应发送到 IoT Hub 的消息,并将数据存储在 Azure Storage 中以供后续分析。
* **行动** - 您已经根据云端的决策控制执行器,并通过发送命令到设备控制它们,同时使用 Azure Maps 可视化数据。
✅ 想一想您使用过的其他物联网设备,例如智能家居设备。该设备及其软件涉及哪些设备、洞察和行动?
这种模式可以根据需要扩展到任意大小,添加更多设备和服务。
### 数据和安全
在定义系统架构时,您需要不断考虑数据和安全性。
* 您的设备发送和接收什么数据?
* 如何保护和加密这些数据?
* 如何控制对设备和云服务的访问?
✅ 想一想您拥有的任何物联网设备的数据安全性。这些数据中有多少是个人数据,需要在传输和存储时保持私密?哪些数据不应该被存储?
## 设计水果质量控制系统
现在让我们将设备、洞察和行动的概念应用到水果质量检测器上,设计一个更大的端到端应用。
假设您被要求构建一个水果质量检测器,用于加工厂。水果通过传送带系统运输,目前员工需要手动检查水果并移除任何未成熟的水果。为了降低成本,工厂老板希望实现自动化。
✅ 随着物联网(以及技术总体发展)的兴起,许多手动工作正在被机器取代。做一些研究:预计有多少工作会因物联网而消失?又会创造多少新的工作来构建物联网设备?
您需要构建一个系统,水果到达传送带时被检测到,然后拍照并使用运行在边缘的 AI 模型进行检查。结果被发送到云端存储,如果水果未成熟,则发出通知以便移除未成熟的水果。
| | |
| - | - |
| **设备** | 检测水果到达传送带<br>拍照并分类水果<br>运行分类器的边缘设备<br>通知未成熟水果的设备 |
| **洞察** | 决定检查水果的成熟度<br>存储成熟度分类的结果<br>确定是否需要发出未成熟水果的警报 |
| **行动** | 发送命令到设备拍摄水果照片并使用图像分类器检查<br>发送命令到设备发出未成熟水果的警报 |
### 应用原型设计
![水果质量检测的参考物联网架构](../../../../../translated_images/zh-CN/iot-reference-architecture-fruit-quality.cc705f121c3b6fa71c800d9630935ac34bc08223a04601e35f41d5e9b5dd5207.png)
上图展示了该原型应用的参考架构。
* 带有接近传感器的物联网设备检测到水果到达。它向云端发送消息,表示检测到水果。
* 云端的无服务器应用向另一设备发送命令,拍摄照片并进行分类。
* 带有摄像头的物联网设备拍摄照片并将其发送到运行在边缘的图像分类器。结果随后发送到云端。
* 云端的无服务器应用存储这些信息,以便稍后分析未成熟水果的百分比。如果水果未成熟,它会向另一个带有 LED 的物联网设备发送命令,通知工厂工人有未成熟水果。
> 💁 整个物联网应用可以作为单个设备实现,所有启动图像分类和控制 LED 的逻辑都内置其中。它可以使用 IoT Hub 仅跟踪检测到的未成熟水果数量并配置设备。在本课中,它被扩展以展示大规模物联网应用的概念。
对于原型,您将在单个设备上实现所有这些功能。如果您使用的是微控制器,则需要使用单独的边缘设备运行图像分类器。您已经学习了构建这些功能所需的大部分内容。
## 从传感器触发水果质量检测
物联网设备需要某种触发器来指示水果准备好进行分类。一个触发器可以是通过测量传感器到水果的距离来判断水果是否在传送带上的正确位置。
![接近传感器通过激光束测量到物体(如香蕉)的距离,并计算光束反射回来的时间](../../../../../translated_images/zh-CN/proximity-sensor.f5cd752c77fb62fe.webp)
接近传感器可以用来测量传感器到物体的距离。它们通常发射电磁辐射束,例如激光束或红外光,然后检测从物体反射回来的辐射。发送激光束和信号反射回来的时间间隔可以用来计算到传感器的距离。
> 💁 您可能已经使用过接近传感器而没有意识到它的存在。大多数智能手机在您将它们贴近耳朵时会关闭屏幕,以防止您用耳垂意外结束通话。这是通过接近传感器实现的,它在通话期间检测到屏幕附近的物体并禁用触摸功能,直到手机离开一定距离。
### 任务 - 使用距离传感器触发水果质量检测
按照相关指南,使用接近传感器检测物体并触发您的物联网设备:
* [Arduino - Wio Terminal](wio-terminal-proximity.md)
* [单板计算机 - Raspberry Pi](pi-proximity.md)
* [单板计算机 - 虚拟设备](virtual-device-proximity.md)
## 水果质量检测器使用的数据
原型水果检测器有多个组件相互通信。
![组件之间的通信](../../../../../translated_images/zh-CN/fruit-quality-detector-message-flow.adf2a65da8fd8741ac7af11361574de89adc126785d67606bb4d2ec00467e380.png)
* 接近传感器测量到水果的距离并将数据发送到 IoT Hub
* 控制摄像头的命令从 IoT Hub 发送到摄像头设备
* 图像分类的结果被发送到 IoT Hub
* 控制 LED 的命令从 IoT Hub 发送到带有 LED 的设备,以警告未成熟水果
在构建应用之前,最好提前定义这些消息的结构。
> 💁 几乎每个有经验的开发者都曾在职业生涯中花费数小时、数天甚至数周追踪由于发送的数据与预期不一致而导致的错误。
例如 - 如果您发送温度信息,如何定义 JSON您可以有一个字段叫 `temperature`,或者使用常见的缩写 `temp`
```json
{
"temperature": 20.7
}
```
与:
```json
{
"temp": 20.7
}
```
您还需要考虑单位 - 温度是以 °C 还是 °F 表示?如果您使用的是消费者设备,并且他们更改了显示单位,您需要确保发送到云端的单位保持一致。
✅ 做一些研究:单位问题如何导致价值 1.25 亿美元的火星气候探测器坠毁?
思考水果质量检测器发送的数据。您如何定义每条消息?在哪里分析数据并决定发送哪些数据?
例如 - 使用接近传感器触发图像分类。物联网设备测量距离,但决策在哪里做出?设备是否决定水果足够接近并发送消息告诉 IoT Hub 触发分类?还是发送距离测量数据并让 IoT Hub 决定?
这些问题的答案是 - 视情况而定。每个用例都不同,这就是为什么作为物联网开发者,您需要了解您正在构建的系统、它的使用方式以及检测到的数据。
* 如果决策由 IoT Hub 做出,您需要发送多个距离测量数据。
* 如果发送过多消息,会增加 IoT Hub 的成本,以及物联网设备所需的带宽(尤其是在拥有数百万设备的工厂中)。这也可能会减慢设备的速度。
* 如果在设备上做出决策,您需要提供一种配置设备的方法以微调机器。
## 使用开发设备模拟多个物联网设备
为了构建您的原型,您需要让您的物联网开发套件像多个设备一样工作,发送遥测数据并响应命令。
### 在 Raspberry Pi 或虚拟物联网硬件上模拟多个物联网设备
使用单板计算机(如 Raspberry Pi您可以同时运行多个应用程序。这意味着您可以通过创建多个应用程序来模拟多个物联网设备每个应用程序代表一个“物联网设备”。例如您可以将每个设备实现为一个单独的 Python 文件,并在不同的终端会话中运行它们。
💁 请注意,当多个应用程序同时运行并访问某些硬件时,可能会导致硬件无法正常工作。
### 在微控制器上模拟多个设备
在微控制器上模拟多个设备会更复杂一些。与单板计算机不同,微控制器无法同时运行多个应用程序,必须将所有独立物联网设备的逻辑整合到一个应用程序中。
以下是一些简化此过程的建议:
* 为每个物联网设备创建一个或多个类,例如名为 `DistanceSensor`、`ClassifierCamera`、`LEDController` 的类。每个类可以有自己的 `setup``loop` 方法,由主程序的 `setup``loop` 函数调用。
* 在一个地方处理所有命令,并根据需要将命令分发到相关的设备类。
* 在主 `loop` 函数中,需要考虑每个设备的时间安排。例如,如果有一个设备类需要每 10 秒处理一次,另一个需要每 1 秒处理一次,那么在主 `loop` 函数中使用 1 秒的延迟。每次 `loop` 调用都会触发需要每秒处理的设备的相关代码,并使用一个计数器记录循环次数,当计数器达到 10 时处理另一个设备(然后重置计数器)。
## 进入生产阶段
原型将成为最终生产系统的基础。在进入生产阶段时,一些不同之处包括:
* 工业级组件 - 使用能够承受工厂中的噪音、热量、振动和压力的硬件。
* 使用内部通信 - 一些组件会直接通信,避免通过云端中转,仅将数据发送到云端存储。这取决于工厂的设置,可以通过直接通信实现,或者通过使用网关设备在边缘运行部分物联网服务实现。
* 配置选项 - 每个工厂和使用场景都不同,因此硬件需要具备可配置性。例如,接近传感器可能需要在不同距离检测不同的水果。与其将触发分类的距离硬编码,不如通过云端(例如使用设备孪生)使其可配置。
* 自动化水果移除 - 不再使用 LED 提示水果未成熟,而是使用自动化设备将其移除。
✅ 做些研究:生产设备与开发套件还有哪些其他不同之处?
---
## 🚀 挑战
在本课中,你学习了一些关于如何设计物联网系统的概念。回想之前的项目,它们如何适配上面展示的参考架构?
选择一个之前的项目,思考如何设计一个更复杂的解决方案,将多个功能结合在一起,超越项目中所涵盖的内容。绘制架构图,并思考所需的所有设备和服务。
例如 - 一个车辆追踪设备,结合 GPS 和传感器来监控冷藏卡车的温度、发动机启停时间以及驾驶员身份。涉及哪些设备、服务、传输的数据以及安全和隐私的考虑?
## 课后测验
[课后测验](https://black-meadow-040d15503.1.azurestaticapps.net/quiz/36)
## 复习与自学
* 阅读更多关于物联网架构的内容,请参阅 [Microsoft Docs 上的 Azure IoT 参考架构文档](https://docs.microsoft.com/azure/architecture/reference-architectures/iot?WT.mc_id=academic-17441-jabenn)
* 阅读更多关于设备孪生的内容,请参阅 [Microsoft Docs 上的 IoT Hub 设备孪生文档](https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins?WT.mc_id=academic-17441-jabenn)
* 了解 OPC-UA这是一种用于工业自动化的机器间通信协议请参阅 [Wikipedia 上的 OPC-UA 页面](https://wikipedia.org/wiki/OPC_Unified_Architecture)
## 作业
[构建一个水果质量检测器](assignment.md)
**免责声明**
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原文档的原始语言版本为权威来源。对于关键信息,建议使用专业人工翻译。我们对因使用本翻译而引起的任何误解或误读不承担责任。

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save