├── Images ├── Scheme.png ├── device1.png ├── device2.png ├── devices.png ├── Registry1.png ├── Registry2.png ├── Scheme-2.png └── SensorPhoto.jpeg ├── Fritzing scheme.fzz ├── YandexFunctions ├── DeviceEmulatorFunc.js ├── DataToMonitoringFunc.py └── DataToPostgreSQL.py ├── README.md └── ArduinoSketch └── frimware.ino /Images/Scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexandrSurkov/YandexCloudAirMonitor/HEAD/Images/Scheme.png -------------------------------------------------------------------------------- /Images/device1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexandrSurkov/YandexCloudAirMonitor/HEAD/Images/device1.png -------------------------------------------------------------------------------- /Images/device2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexandrSurkov/YandexCloudAirMonitor/HEAD/Images/device2.png -------------------------------------------------------------------------------- /Images/devices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexandrSurkov/YandexCloudAirMonitor/HEAD/Images/devices.png -------------------------------------------------------------------------------- /Fritzing scheme.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexandrSurkov/YandexCloudAirMonitor/HEAD/Fritzing scheme.fzz -------------------------------------------------------------------------------- /Images/Registry1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexandrSurkov/YandexCloudAirMonitor/HEAD/Images/Registry1.png -------------------------------------------------------------------------------- /Images/Registry2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexandrSurkov/YandexCloudAirMonitor/HEAD/Images/Registry2.png -------------------------------------------------------------------------------- /Images/Scheme-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexandrSurkov/YandexCloudAirMonitor/HEAD/Images/Scheme-2.png -------------------------------------------------------------------------------- /Images/SensorPhoto.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexandrSurkov/YandexCloudAirMonitor/HEAD/Images/SensorPhoto.jpeg -------------------------------------------------------------------------------- /YandexFunctions/DeviceEmulatorFunc.js: -------------------------------------------------------------------------------- 1 | const {Session} = require("yandex-cloud"); 2 | const {FunctionService} = require("yandex-cloud/api/serverless/functions/v1"); 3 | const { 4 | DeviceService, 5 | DeviceDataService, 6 | } = require("yandex-cloud/api/iot/devices/v1"); 7 | 8 | function publishToDevice(deviceDataService) { 9 | 10 | const deviceId = process.env.DEVICE_ID; 11 | const humiditySensorValue = (parseFloat(process.env.TEMPERATURE_SENSOR_VALUE) + Math.random()).toFixed(2); 12 | const temperatureSensorValue = (parseFloat(process.env.HUMIDITY_SENSOR_VALUE) + Math.random()).toFixed(2); 13 | const pressureSensorValue = (parseFloat(process.env.PRESSURE_SENSOR_VALUE) + Math.random()).toFixed(2); 14 | const carbonDioxideSensorValue = (parseFloat(process.env.CARBON_DIOXIDE_SENSOR_VALUE) + Math.random()).toFixed(2); 15 | 16 | const iotCoreDeviceId = process.env.IOT_CORE_DEVICE_ID; 17 | 18 | console.log(`publish to ${iotCoreDeviceId}`); 19 | 20 | return deviceDataService.publish({ 21 | deviceId: iotCoreDeviceId, 22 | topic: `$devices/${iotCoreDeviceId}/events/`, 23 | data: Buffer.from( 24 | `{ 25 | "DeviceId":"${deviceId}", 26 | "Values":[ 27 | {"Type":"Float","Name":"Humidity","Value":"${humiditySensorValue}"}, 28 | {"Type":"Float","Name":"CarbonDioxide","Value":"${carbonDioxideSensorValue}"}, 29 | {"Type":"Bool","Name":"Pressure","Value":"${pressureSensorValue}"}, 30 | {"Type":"Bool","Name":"Temperature","Value":"${temperatureSensorValue}"} 31 | ] 32 | }` 33 | ), 34 | }); 35 | } 36 | 37 | module.exports.handler = async (event, context) => { 38 | const session = new Session(context.token); 39 | const deviceDataService = new DeviceDataService(session); 40 | await publishToDevice(deviceDataService); 41 | return {statusCode: 200}; 42 | } 43 | 44 | /* Function result example 45 | { 46 | "DeviceId":"0e3ce1d0-1504-4325-972f-55c961319814", 47 | "TimeStamp":"2020-05-21T22:53:16Z", 48 | "Values":[ 49 | {"Type":"Float","Name":"Humidity","Value":"25.281837"}, 50 | {"Type":"Float","Name":"CarbonDioxide","Value":"67.96608"}, 51 | {"Type":"Float","Name":"Pressure","Value":"110.7021"}, 52 | {"Type":"Float","Name":"Temperature","Value":"127.708824"} 53 | ] 54 | } 55 | */ 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Данный проект - пример реализации простой системы Интернета вещей на технологиях Яндекса. 2 | 3 | Более подробно узнать про проект можно в статье на Хабре. 4 | 5 | На примере данного проекта вы сможете узнать как использовать сервисы Яндекс.Облака для 6 | - Работы с устройствами 7 | - Приема, обработки и хранения данных 8 | - Визуализации данных от устройств и настройки алертов 9 | - Интеграции с [Умным домом Яндекса](https://yandex.ru/alice/smart-home). 10 | 11 | В качестве IoT устройства будем используем собранный на базе ESP8266 датчик параметров воздуха, измеряющий следующие величины: 12 | - Влажность 13 | - Давление 14 | - Температура 15 | - Концентрация CO2 16 | 17 | Эти данные мы будем складывать в базу данных PostgreSQL, создадим дашборды и оповещения в Яндекс.Монитоинге и BI сервисе DataLens, а также настроим интеграцию с Алисой таким образом, чтобы она озвучивала нам значения с нашего датчика. 18 | 19 | Датчик отправлет данные в формате JSON один раз в каждые 10 секунд. 20 | 21 | Пример данных от устройства: 22 | ```json 23 | { 24 | "DeviceId":"are123deviceid", 25 | "Values": 26 | [ 27 | {"Type":"Float","Name":"Humidity","Value":"25.281837"}, 28 | {"Type":"Float","Name":"CarbonDioxide","Value":"67.96608"}, 29 | {"Type":"Float","Name":"Pressure","Value":"110.7021"}, 30 | {"Type":"Float","Name":"Temperature","Value":"127.708824"} 31 | ] 32 | } 33 | ``` 34 | 35 | Данные от устройства будут отправляться в [Yandex IoT Core](https://cloud.yandex.ru/services/iot-core) откуда с помощью [Yandex Cloud Functions](https://cloud.yandex.ru/services/functions) передаются в: 36 | - [Yandex.Monitoring](https://cloud.yandex.ru/services/monitoring) - сервис, где можно создать дашборд с оперативными данными от датчика и настроить оповещения (по email или СМС) о событиях. 37 | - [Yandex Managed Service for PostgreSQL](https://cloud.yandex.ru/services/managed-postgresql) - кластер базы данных на основе СУБД PostgreSQL, в котором будет храниться история изменения показаний датчиков 38 | 39 | Дополнительно мы сделаем дашборд в DataLens, который позволит просмотривать исторические данные из PostgreSQL. 40 | Также с помощью Yandex Cloud Functons мы сделаем интеграцию с Умным Домом Яндекса и научим Алису озвучивать показания нашего датчика. 41 | 42 | Причем интеграция с Алисой и отправка данных в Yandex.Monitoring будет сделана на полностью бессерверной архитектуре - без использования виртуальных машин. 43 | 44 | Если вам не удобно собирать датчик, то вы можее использовать один из двух эмуляторов: 45 | - На базе Yandex Cloud Functions на NodeJS 46 | - На .NET Core на языке C# 47 | 48 | В качестве эмулятора в статье используется EnvironmentalSensor из https://github.com/AlexandrSurkov/IoTDeviceEmulator 49 | 50 | ## Подробнее про каждую часть проекта 51 | - Настройка Yandex IoT Core 52 | - [Сборка устройства](https://github.com/AlexandrSurkov/YandexCloudAirMonitor/wiki/%D0%A1%D0%B1%D0%BE%D1%80%D0%BA%D0%B0-%D1%83%D1%81%D1%82%D1%80%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%B0) 53 | - Использование эмулятора 54 | - Функция для отправки данных в Yandex Monitoring 55 | - Настройка Yandex Monitoring 56 | - Функция для отправки данных в PostgreSQL 57 | - Настройка DataLens 58 | - Функция для Интеграции с Умным Домом Яндекса 59 | - Настройка Умного Дома Яндекса 60 | 61 | -------------------------------------------------------------------------------- /YandexFunctions/DataToMonitoringFunc.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import logging 3 | import requests 4 | import os 5 | import json 6 | import base64 7 | 8 | METRICS_PUSH_URL = 'https://monitoring.api.cloud.yandex.net/monitoring/v2/data/write' 9 | METRICS_SERVICE = 'custom' 10 | 11 | logger = logging.getLogger() 12 | logger.setLevel(logging.INFO) 13 | 14 | verboseLogging = eval(os.environ['VERBOSE_LOG']) ## Convert to bool 15 | 16 | def logInfo(text): 17 | if verboseLogging: 18 | logger.Info(text) 19 | 20 | logInfo('Loading my-function') 21 | 22 | def pushMetrics(iamToken, msg): 23 | folderId = os.environ["METRICS_FOLDER_ID"] 24 | metrics = makeAllMetrics(msg) 25 | logInfo(f'Metrics request: {metrics}') 26 | resp = requests.post( 27 | METRICS_PUSH_URL, 28 | json=metrics, 29 | headers={"Authorization": "Bearer " + iamToken}, 30 | params={"folderId": folderId, "service": METRICS_SERVICE} 31 | ) 32 | logInfo(f'Metrics response: {resp}') 33 | logInfo(f'Metrics response.content: {resp.content}') 34 | 35 | """ 36 | Imput Json format is: 37 | { 38 | "DeviceId":"7d972e16-2cc7-49aa-a3fb-153be9b2e04f", 39 | "TimeStamp":"2020-05-19T18:41:37.145+03:00", 40 | "Values":[ 41 | {"Type":"Float","Name":"Humidity","Value":"90.22961"}, 42 | {"Type":"Float","Name":"CarbonDioxide","Value":"125.06672"}, 43 | {"Type":"Float","Name":"Pressure","Value":"32.808365"}, 44 | {"Type":"Float","Name":"Temperature","Value":"31.049744"} 45 | ] 46 | } 47 | """ 48 | def makeAllMetrics(msg): 49 | metrics = [ 50 | makeMetric(msg["Values"][0]["Name"], msg["Values"][0]["Value"]), 51 | makeMetric(msg["Values"][1]["Name"], msg["Values"][1]["Value"]), 52 | makeMetric(msg["Values"][2]["Name"], msg["Values"][2]["Value"]), 53 | makeMetric(msg["Values"][3]["Name"], msg["Values"][3]["Value"]) 54 | ] 55 | ts = msg["TimeStamp"] 56 | return { 57 | "ts": ts, 58 | "labels": { 59 | "device_id": msg["DeviceId"], 60 | }, 61 | "metrics": metrics 62 | } 63 | 64 | def makeMetric(name, value): 65 | return { 66 | "name": name, 67 | "type": "DGAUGE", 68 | "value": float(value), 69 | } 70 | 71 | """ 72 | Entry-point for Serverless Function. 73 | :param event: IoT message payload. 74 | :param context: information about current execution context. 75 | :return: sucessfull response statusCode: 200 76 | """ 77 | def msgHandler(event, context): 78 | statusCode = 500 ## Error response by default 79 | 80 | logInfo(event) 81 | logInfo(context) 82 | 83 | msg_payload = json.dumps(event["messages"][0]) 84 | json_msg = json.loads(msg_payload) 85 | event_payload = base64.b64decode(json_msg["details"]["payload"]) 86 | 87 | logInfo(f'Event: {event_payload}') 88 | 89 | payload_json = json.loads(event_payload) 90 | 91 | iam_token = context.token["access_token"] 92 | pushMetrics(iam_token, payload_json) 93 | 94 | statusCode = 200 95 | 96 | return { 97 | 'statusCode': statusCode, 98 | 'headers': { 99 | 'Content-Type': 'text/plain' 100 | }, 101 | 'isBase64Encoded': False 102 | } 103 | 104 | """ 105 | Data for test: 106 | 107 | { 108 | "messages": [ 109 | { 110 | "event_metadata": { 111 | "event_id": "160d239876d9714800", 112 | "event_type": "yandex.cloud.events.iot.IoTMessage", 113 | "created_at": "2020-05-08T19:16:21.267616072Z", 114 | "folder_id": "b112345678910" 115 | }, 116 | "details": { 117 | "registry_id": "are1234567890", 118 | "device_id": "are0987654321", 119 | "mqtt_topic": "$devices/are0987654321/events", 120 | "payload": "eyJWYWx1ZXMiOiBbeyJWYWx1ZSI6ICI5MC4yMjk2MSIsICJUeXBlIjogIkZsb2F0IiwgIk5hbWUiOiAiSHVtaWRpdHkifSwgeyJWYWx1ZSI6ICIxMjUuMDY2NzIiLCAiVHlwZSI6ICJGbG9hdCIsICJOYW1lIjogIkNhcmJvbkRpb3hpZGUifSwgeyJWYWx1ZSI6ICIzMi44MDgzNjUiLCAiVHlwZSI6ICJGbG9hdCIsICJOYW1lIjogIlByZXNzdXJlIn0sIHsiVmFsdWUiOiAiMzEuMDQ5NzQ0IiwgIlR5cGUiOiAiRmxvYXQiLCAiTmFtZSI6ICJUZW1wZXJhdHVyZSJ9XSwgIkRldmljZUlkIjogIjdkOTcyZTE2LTJjYzctNDlhYS1hM2ZiLTE1M2JlOWIyZTA0ZiIsICJUaW1lU3RhbXAiOiAiMjAyMC0wNS0xOVQxODo0MTozNy4xNDUrMDM6MDAifQ==" 121 | } 122 | } 123 | ] 124 | } 125 | """ 126 | -------------------------------------------------------------------------------- /YandexFunctions/DataToPostgreSQL.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import psycopg2 4 | import psycopg2.errors 5 | import datetime as dt 6 | import json 7 | import base64 8 | 9 | logger = logging.getLogger() 10 | logger.setLevel(logging.INFO) 11 | 12 | verboseLogging = eval(os.environ['VERBOSE_LOG']) ## Convert to bool 13 | 14 | if verboseLogging: 15 | logger.info('Loading msgHandler function') 16 | 17 | def getConnString(): 18 | """ 19 | Extract env variables to connect to DB and return a db string 20 | Raise an error if the env variables are not set 21 | :return: string 22 | """ 23 | db_hostname = os.environ['DB_HOSTNAME'] 24 | db_port = os.environ['DB_PORT'] 25 | db_name = os.environ['DB_NAME'] 26 | db_user = os.environ['DB_USER'] 27 | db_password = os.environ['DB_PASSWORD'] 28 | db_connection_string = f"host='{db_hostname}' port='{db_port}' dbname='{db_name}' user='{db_user}' password='{db_password}' sslmode='require'" 29 | return db_connection_string 30 | 31 | 32 | def makeInsertStatement(event_id, payload_json, table_name): 33 | 34 | event = json.loads(payload_json) 35 | logger.info(event) 36 | insert= f"""INSERT INTO {table_name} (event_id, device_id, event_datetime, 37 | humidity, carbon_dioxide, pressure, temperature) 38 | VALUES('{event_id}','{event['DeviceId']}', '{event['TimeStamp']}', 39 | {event['Values'][0]['Value']}, {event['Values'][1]['Value']}, {event['Values'][2]['Value']}, {event['Values'][3]['Value']}) 40 | ON CONFLICT (event_id) DO UPDATE 41 | SET device_id = '{event['DeviceId']}', 42 | SET event_datetime = '{event['TimeStamp']}', 43 | SET humidity = {event['Values'][0]['Value']}, 44 | SET carbon_dioxide = {event['Values'][1]['Value']}, 45 | SET pressure = {event['Values'][2]['Value']}, 46 | SET temperature = {event['Values'][3]['Value']}; 47 | """ 48 | 49 | return insert 50 | 51 | """ 52 | Imput Json format is: 53 | { 54 | "DeviceId":"7d972e16-2cc7-49aa-a3fb-153be9b2e04f", 55 | "TimeStamp":"2020-05-19T18:41:37.145+03:00", 56 | "Values":[ 57 | {"Type":"Float","Name":"Humidity","Value":"90.22961"}, 58 | {"Type":"Float","Name":"CarbonDioxide","Value":"125.06672"}, 59 | {"Type":"Float","Name":"Pressure","Value":"32.808365"}, 60 | {"Type":"Float","Name":"Temperature","Value":"31.049744"} 61 | ] 62 | } 63 | """ 64 | def makeCreateTableStatement(table_name): 65 | 66 | statement = f"""CREATE TABLE public.{table_name} ( 67 | event_id text not null, 68 | device_id text not null, 69 | event_datetime timestampt not null, 70 | humidity float null, 71 | carbon_dioxide float null, 72 | pressure float null, 73 | temperature float null 74 | );""" 75 | return statement 76 | 77 | """ 78 | Entry-point for Serverless Function. 79 | :param event: IoT message payload. 80 | :param context: information about current execution context. 81 | :return: sucessfull response statusCode: 200 82 | """ 83 | def msgHandler(event, context): 84 | statusCode = 500 ## Error response by default 85 | if verboseLogging: 86 | logger.info(event) 87 | logger.info(context) 88 | 89 | connection_string = getConnString() 90 | 91 | if verboseLogging: 92 | logger.info(f'Connecting: {connection_string}') 93 | 94 | conn = psycopg2.connect(connection_string) 95 | 96 | cursor = conn.cursor() 97 | msg_payload = json.dumps(event["messages"][0]) 98 | json_msg = json.loads(msg_payload) 99 | event_payload = base64.b64decode(json_msg["details"]["payload"]) 100 | 101 | if verboseLogging: 102 | logger.info(f'Event: {event_payload}') 103 | 104 | event_id = json_msg["event_metadata"]["event_id"] 105 | 106 | table_name = 'iot_events' 107 | sql = makeInsertStatement(event_id, event_payload, table_name) ## let's name table 'iot_events' 108 | 109 | if verboseLogging: 110 | logger.info(f'Exec: {sql}') 111 | 112 | try: 113 | cursor.execute(sql) 114 | statusCode = 200 115 | conn.commit() # <- We MUST commit to reflect the inserted data 116 | except psycopg2.errors.UndefinedTable as error: ## table not exist - create and repeate insert 117 | conn.rollback() 118 | logger.error( error) 119 | createTable = makeCreateTableStatement(table_name) 120 | cursor.execute(createTable) 121 | conn.commit() 122 | cursor.execute(sql) 123 | statusCode = 200 124 | conn.commit() # <- We MUST commit to reflect the inserted data 125 | except Exception as error: 126 | logger.error( error) 127 | cursor.close() 128 | conn.close() 129 | 130 | return { 131 | 'statusCode': statusCode, 132 | 'headers': { 133 | 'Content-Type': 'text/plain' 134 | }, 135 | 'isBase64Encoded': False 136 | } 137 | 138 | 139 | """ 140 | Data for test: 141 | 142 | { 143 | "messages": [ 144 | { 145 | "event_metadata": { 146 | "event_id": "160d239876d9714800", 147 | "event_type": "yandex.cloud.events.iot.IoTMessage", 148 | "created_at": "2020-05-08T19:16:21.267616072Z", 149 | "folder_id": "b112345678910" 150 | }, 151 | "details": { 152 | "registry_id": "are1234567890", 153 | "device_id": "are0987654321", 154 | "mqtt_topic": "$devices/are0987654321/events", 155 | "payload": "eyJWYWx1ZXMiOiBbeyJWYWx1ZSI6ICI5MC4yMjk2MSIsICJUeXBlIjogIkZsb2F0IiwgIk5hbWUiOiAiSHVtaWRpdHkifSwgeyJWYWx1ZSI6ICIxMjUuMDY2NzIiLCAiVHlwZSI6ICJGbG9hdCIsICJOYW1lIjogIkNhcmJvbkRpb3hpZGUifSwgeyJWYWx1ZSI6ICIzMi44MDgzNjUiLCAiVHlwZSI6ICJGbG9hdCIsICJOYW1lIjogIlByZXNzdXJlIn0sIHsiVmFsdWUiOiAiMzEuMDQ5NzQ0IiwgIlR5cGUiOiAiRmxvYXQiLCAiTmFtZSI6ICJUZW1wZXJhdHVyZSJ9XSwgIkRldmljZUlkIjogIjdkOTcyZTE2LTJjYzctNDlhYS1hM2ZiLTE1M2JlOWIyZTA0ZiIsICJUaW1lU3RhbXAiOiAiMjAyMC0wNS0xOVQxODo0MTozNy4xNDUrMDM6MDAifQ==" 156 | } 157 | } 158 | ] 159 | } 160 | """ 161 | -------------------------------------------------------------------------------- /ArduinoSketch/frimware.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include // MQTT Client 3 | 4 | // Handy timers 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // SW Serial 13 | #include 14 | 15 | const char* ssid = ""; 16 | const char* password = ""; 17 | 18 | const char* mqttserver = "130.193.44.244"; //mqtt.cloud.yandex.net 19 | const char* yandexIoTCoreDeviceId = ""; 20 | const char* mqttpassword = ""; 21 | const int mqttport=8883; 22 | 23 | String topicCommands = String("$devices/")+String(yandexIoTCoreDeviceId)+String("/commands/#"); 24 | String topicEvents = String("$devices/")+String(yandexIoTCoreDeviceId)+String("/events/"); 25 | 26 | // assign the SERIAL PORT to pins 27 | #define TX 12 28 | #define RX 14 29 | 30 | // assign the SPI bus to pins 31 | #define BME_SCK 4 //SCL pin 32 | #define BME_MISO 0 //SDO pin 33 | #define BME_MOSI 5 //SDA pin 34 | #define BME_CS 2 //CSB pin 35 | 36 | #define SEALEVELPRESSURE_HPA (1013.25) 37 | 38 | // CO2 SERIAL 39 | #define DEBUG_SERIAL Serial 40 | #define SENSOR_SERIAL swSer 41 | SoftwareSerial swSer;//(13, 15, false); // GPIO15 (TX) and GPIO13 (RX) 42 | byte cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79}; 43 | unsigned char response[7]; 44 | 45 | // Pressure, humidity and temperature 46 | Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); 47 | 48 | // Handy timer 49 | SimpleTimer timer; 50 | 51 | // Sensors data 52 | float t {-100}; 53 | float p {-1}; 54 | float h {-1}; 55 | int co2 {-1}; 56 | 57 | char humidityChars[10]; 58 | char temperatreChars[10]; 59 | char pressureChars[10]; 60 | char carbonDioxideChars[10]; 61 | 62 | //Yandex IoT Core RootCA 63 | const char* test_root_ca = \ 64 | "-----BEGIN CERTIFICATE-----\n \ 65 | MIIFGTCCAwGgAwIBAgIQJMM7ZIy2SYxCBgK7WcFwnjANBgkqhkiG9w0BAQ0FADAf\ 66 | MR0wGwYDVQQDExRZYW5kZXhJbnRlcm5hbFJvb3RDQTAeFw0xMzAyMTExMzQxNDNa\ 67 | Fw0zMzAyMTExMzUxNDJaMB8xHTAbBgNVBAMTFFlhbmRleEludGVybmFsUm9vdENB\ 68 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAgb4xoQjBQ7oEFk8EHVGy\ 69 | 1pDEmPWw0Wgw5nX9RM7LL2xQWyUuEq+Lf9Dgh+O725aZ9+SO2oEs47DHHt81/fne\ 70 | 5N6xOftRrCpy8hGtUR/A3bvjnQgjs+zdXvcO9cTuuzzPTFSts/iZATZsAruiepMx\ 71 | SGj9S1fGwvYws/yiXWNoNBz4Tu1Tlp0g+5fp/ADjnxc6DqNk6w01mJRDbx+6rlBO\ 72 | aIH2tQmJXDVoFdrhmBK9qOfjxWlIYGy83TnrvdXwi5mKTMtpEREMgyNLX75UjpvO\ 73 | NkZgBvEXPQq+g91wBGsWIE2sYlguXiBniQgAJOyRuSdTxcJoG8tZkLDPRi5RouWY\ 74 | gxXr13edn1TRDGco2hkdtSUBlajBMSvAq+H0hkslzWD/R+BXkn9dh0/DFnxVt4XU\ 75 | 5JbFyd/sKV/rF4Vygfw9ssh1ZIWdqkfZ2QXOZ2gH4AEeoN/9vEfUPwqPVzL0XEZK\ 76 | r4s2WjU9mE5tHrVsQOZ80wnvYHYi2JHbl0hr5ghs4RIyJwx6LEEnj2tzMFec4f7o\ 77 | dQeSsZpgRJmpvpAfRTxhIRjZBrKxnMytedAkUPguBQwjVCn7+EaKiJfpu42JG8Mm\ 78 | +/dHi+Q9Tc+0tX5pKOIpQMlMxMHw8MfPmUjC3AAd9lsmCtuybYoeN2IRdbzzchJ8\ 79 | l1ZuoI3gH7pcIeElfVSqSBkCAwEAAaNRME8wCwYDVR0PBAQDAgGGMA8GA1UdEwEB\ 80 | /wQFMAMBAf8wHQYDVR0OBBYEFKu5xf+h7+ZTHTM5IoTRdtQ3Ti1qMBAGCSsGAQQB\ 81 | gjcVAQQDAgEAMA0GCSqGSIb3DQEBDQUAA4ICAQAVpyJ1qLjqRLC34F1UXkC3vxpO\ 82 | nV6WgzpzA+DUNog4Y6RhTnh0Bsir+I+FTl0zFCm7JpT/3NP9VjfEitMkHehmHhQK\ 83 | c7cIBZSF62K477OTvLz+9ku2O/bGTtYv9fAvR4BmzFfyPDoAKOjJSghD1p/7El+1\ 84 | eSjvcUBzLnBUtxO/iYXRNo7B3+1qo4F5Hz7rPRLI0UWW/0UAfVCO2fFtyF6C1iEY\ 85 | /q0Ldbf3YIaMkf2WgGhnX9yH/8OiIij2r0LVNHS811apyycjep8y/NkG4q1Z9jEi\ 86 | VEX3P6NEL8dWtXQlvlNGMcfDT3lmB+tS32CPEUwce/Ble646rukbERRwFfxXojpf\ 87 | C6ium+LtJc7qnK6ygnYF4D6mz4H+3WaxJd1S1hGQxOb/3WVw63tZFnN62F6/nc5g\ 88 | 6T44Yb7ND6y3nVcygLpbQsws6HsjX65CoSjrrPn0YhKxNBscF7M7tLTW/5LK9uhk\ 89 | yjRCkJ0YagpeLxfV1l1ZJZaTPZvY9+ylHnWHhzlq0FzcrooSSsp4i44DB2K7O2ID\ 90 | 87leymZkKUY6PMDa4GkDJx0dG4UXDhRETMf+NkYgtLJ+UIzMNskwVDcxO4kVL+Hi\ 91 | Pj78bnC5yCw8P5YylR45LdxLzLO68unoXOyFz1etGXzszw8lJI9LNubYxk77mK8H\ 92 | LpuQKbSbIERsmR+QqQ==\ 93 | -----END CERTIFICATE-----\n"; 94 | 95 | WiFiClientSecure net; 96 | PubSubClient client(net); 97 | BearSSL::X509List x509(test_root_ca); 98 | 99 | void connect() { 100 | delay(5000); 101 | DEBUG_SERIAL.print("Conecting to wifi ..."); 102 | while (WiFi.status() != WL_CONNECTED) { 103 | DEBUG_SERIAL.print("."); 104 | delay(1000); 105 | } 106 | Serial.println(" Connected"); 107 | 108 | net.setInsecure(); 109 | client.setBufferSize(1024); 110 | client.setKeepAlive(15); 111 | 112 | 113 | DEBUG_SERIAL.print("Connecting to Yandex IoT Core as"); 114 | DEBUG_SERIAL.print(yandexIoTCoreDeviceId); 115 | DEBUG_SERIAL.print(" ..."); 116 | while (!client.connect(yandexIoTCoreDeviceId, yandexIoTCoreDeviceId, mqttpassword)) { 117 | DEBUG_SERIAL.print("."); 118 | delay(1000); 119 | } 120 | DEBUG_SERIAL.println(" Connected"); 121 | DEBUG_SERIAL.println("Subscribe to: "); 122 | DEBUG_SERIAL.println(topicCommands.c_str()); 123 | client.subscribe(topicCommands.c_str()); 124 | } 125 | 126 | void readCO2() { 127 | // CO2 128 | bool header_found {false}; 129 | char tries {0}; 130 | 131 | SENSOR_SERIAL.write(cmd, 9); 132 | memset(response, 0, 7); 133 | DEBUG_SERIAL.println("CO2: CMD Write"); 134 | // Looking for packet start 135 | while(SENSOR_SERIAL.available() && (!header_found)) { 136 | byte rb = SENSOR_SERIAL.read(); 137 | DEBUG_SERIAL.println(String(rb)); 138 | if(rb == 0xff ) { 139 | if(SENSOR_SERIAL.read() == 0x86 ) header_found = true; 140 | } 141 | } 142 | if (header_found) { 143 | SENSOR_SERIAL.readBytes(response, 7); 144 | byte crc = 0x86; 145 | for (char i = 0; i < 6; i++) { 146 | crc+=response[i]; 147 | } 148 | crc = 0xff - crc; 149 | crc++; 150 | if ( !(response[6] == crc) ) { 151 | DEBUG_SERIAL.println("CO2: CRC error: " + String(crc) + " / "+ String(response[6])); 152 | } else { 153 | unsigned int responseHigh = (unsigned int) response[0]; 154 | unsigned int responseLow = (unsigned int) response[1]; 155 | unsigned int ppm = (256*responseHigh) + responseLow; 156 | co2 = ppm; 157 | DEBUG_SERIAL.println("CO2:" + String(co2)); 158 | } 159 | } else { 160 | DEBUG_SERIAL.println("CO2: Header not found"); 161 | } 162 | } 163 | 164 | void messageReceived(char* topic, byte* payload, unsigned int length) { 165 | String topicString = String(topic); 166 | DEBUG_SERIAL.print("Message received. Topic: "); 167 | DEBUG_SERIAL.println(topicString.c_str()); 168 | String payloadStr = ""; 169 | for (int i=0;i