├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bme280.c ├── bme280.h ├── config.h ├── main.c ├── setup.sh ├── telemetry.c ├── telemetry.h ├── wiring.c └── wiring.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # vim temp 12 | *.swp 13 | 14 | #cmake file 15 | CMakeCache.txt 16 | CMakeFiles/ 17 | Makefile 18 | app 19 | cmake_install.cmake 20 | 21 | # Libraries 22 | *.lib 23 | *.a 24 | *.la 25 | *.lo 26 | 27 | # Shared objects (inc. Windows DLLs) 28 | *.dll 29 | *.so 30 | *.so.* 31 | *.dylib 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | *.i*86 38 | *.x86_64 39 | *.hex 40 | 41 | # Debug files 42 | *.dSYM/ 43 | *.su 44 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | compiler: gcc 3 | sudo: true 4 | os: linux 5 | branches: 6 | only: 7 | - master 8 | before_script: 9 | - sudo apt-get install python 10 | - sudo pip install cpplint 11 | script: 12 | - cpplint --linelength=120 --filter=-whitespace/braces,-whitespace/newline,-readability/casting,-runtime/threadsafe_fn *.c *.h 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #Copyright (c) Microsoft. All rights reserved. 2 | #Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | cmake_minimum_required(VERSION 2.8.11) 5 | 6 | if (CMAKE_C_COMPILER_ID STREQUAL "GNU") 7 | set (CMAKE_C_FLAGS "--std=c99 -g ${CMAKE_C_FLAGS}") 8 | endif() 9 | 10 | include_directories("/usr/local/include/azureiot" 11 | "/usr/local/include/azureiot/inc/") 12 | 13 | set(SOURCE main.c bme280.c wiring.c telemetry.c parson.c config.h bme280.h wiring.h telemetry.h parson.h) 14 | add_executable(app ${SOURCE}) 15 | target_link_libraries(app wiringPi 16 | serializer 17 | iothub_client 18 | iothub_client_mqtt_transport 19 | umqtt 20 | aziotsharedutil 21 | ssl 22 | crypto 23 | curl 24 | pthread 25 | m 26 | ssl 27 | crypto) 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | services: iot-hub 3 | platforms: C 4 | author: shizn 5 | --- 6 | 7 | # IoT Hub Raspberry Pi 3 Client application 8 | [![Build Status](https://travis-ci.com/Azure-Samples/iot-hub-c-raspberrypi-client-app.svg?token=5ZpmkzKtuWLEXMPjmJ6P&branch=master)](https://travis-ci.com/Azure-Samples/iot-hub-c-raspberrypi-client-app) 9 | 10 | > This repo contains the source code to help you get started with Azure IoT using the Microsoft IoT Pack for Raspberry Pi 3 Starter Kit. You will find the [full tutorial on Docs.microsoft.com](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-raspberry-pi-kit-c-get-started). 11 | 12 | This repo contains an arduino application that runs on Raspberry Pi 3 with a BME280 temperature&humidity sensor, and then sends these data to your IoT hub. At the same time, this application receives Cloud-to-Device messages from your IoT hub, and takes actions according to the C2D command. 13 | 14 | ## Set up your Pi 15 | ### Enable SSH on your Pi 16 | Follow [this page](https://www.raspberrypi.org/documentation/remote-access/ssh/) to enable SSH on your Pi. 17 | 18 | ### Enable SPI on your Pi 19 | Follow [this page](https://www.raspberrypi.org/documentation/configuration/raspi-config.md) to enable SPI on your Pi 20 | 21 | ## Connect your sensor with your Pi 22 | ### Connect with a physical BEM280 sensor and LED 23 | You can follow the image to connect your BME280 and a LED with your Raspberry Pi 3. 24 | 25 | ![BME280](https://docs.microsoft.com/en-us/azure/iot-hub/media/iot-hub-raspberry-pi-kit-c-get-started/3_raspberry-pi-sensor-connection.png) 26 | 27 | ## Download and setup the client app 28 | 29 | 1. Clone the client application to local: 30 | 31 | ```bash 32 | sudo apt-get install git-core 33 | 34 | git clone https://github.com/Azure-Samples/iot-hub-c-raspberrypi-client-app.git 35 | ``` 36 | 37 | 2. Run setup script: 38 | 39 | ```bash 40 | cd ./iot-hub-c-raspberrypi-client-app 41 | 42 | sudo chmod u+x setup.sh 43 | 44 | sudo ./setup.sh 45 | ``` 46 | 47 | **If you don't have a physical BME280, you can use '--simulated-data' as command line parameter to simulate temperature&humidity data.** 48 | 49 | ```bash 50 | sudo ./setup.sh --simulated-data 51 | ``` 52 | 53 | ## Run your client application 54 | Run the client application with root priviledge, and you also need provide your Azure IoT hub device connection string, note your connection should be quoted in the command. 55 | 56 | ```bash 57 | sudo ./app '' 58 | ``` 59 | 60 | ### Send Cloud-to-Device command 61 | You can send a C2D message to your device. You can see the device prints out the message and blinks once when receiving the message. 62 | 63 | ### Send Device Method command 64 | You can send `start` or `stop` device method command to your Pi to start/stop sending message to your IoT hub. 65 | -------------------------------------------------------------------------------- /bme280.c: -------------------------------------------------------------------------------- 1 | /* 2 | * IoT Hub Raspberry Pi C - Microsoft Sample Code - Copyright (c) 2017 - Licensed MIT 3 | */ 4 | 5 | /////////////////////////////////////////////////////////////////////////////// 6 | // 7 | // bme280.c: 8 | // SPI based interface to read temperature, pressure and humidity samples from 9 | // a BME280 module. 10 | // 11 | /////////////////////////////////////////////////////////////////////////////// 12 | 13 | #include "./bme280.h" 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | #define SENSOR_MODULE_MAX_XFER_LEN (128) 22 | static int Num_allowed_retries__i = 3; 23 | static int Chip_enable_selected__i = -1; 24 | 25 | // #define SHOW_DEBUG_OUTPUT 26 | 27 | 28 | /////////////////////////////////////////////////////////////////////////////// 29 | // Device registers 30 | enum 31 | { 32 | eBME280reg_DIG_T1 = 0x88 33 | , eBME280reg_DIG_T2 = 0x8A 34 | , eBME280reg_DIG_T3 = 0x8C 35 | 36 | , eBME280reg_DIG_P1 = 0x8E 37 | , eBME280reg_DIG_P2 = 0x90 38 | , eBME280reg_DIG_P3 = 0x92 39 | , eBME280reg_DIG_P4 = 0x94 40 | , eBME280reg_DIG_P5 = 0x96 41 | , eBME280reg_DIG_P6 = 0x98 42 | , eBME280reg_DIG_P7 = 0x9A 43 | , eBME280reg_DIG_P8 = 0x9C 44 | , eBME280reg_DIG_P9 = 0x9E 45 | 46 | , eBME280reg_DIG_H1 = 0xA1 47 | , eBME280reg_DIG_H2 = 0xE1 48 | , eBME280reg_DIG_H3 = 0xE3 49 | , eBME280reg_DIG_H4 = 0xE4 50 | , eBME280reg_DIG_H5 = 0xE5 51 | , eBME280reg_DIG_H6 = 0xE7 52 | 53 | , eBME280reg_CHIPID = 0xD0 54 | , eBME280reg_VERSION = 0xD1 55 | , eBME280reg_SWRESET = 0xE0 56 | 57 | , eBME280reg_STATUS = 0xF3 58 | , eBME280reg_CONTROL = 0xF4 59 | , eBME280reg_CONFIG = 0xF5 60 | , eBME280reg_PRESDATA = 0xF7 61 | , eBME280reg_TEMPDATA = 0xFA 62 | }; 63 | 64 | 65 | // Calibration data as read from the device. 66 | typedef struct 67 | { 68 | uint16_t dig_T1; 69 | int16_t dig_T2; 70 | int16_t dig_T3; 71 | 72 | uint16_t dig_P1; 73 | int16_t dig_P2; 74 | int16_t dig_P3; 75 | int16_t dig_P4; 76 | int16_t dig_P5; 77 | int16_t dig_P6; 78 | int16_t dig_P7; 79 | int16_t dig_P8; 80 | int16_t dig_P9; 81 | 82 | uint8_t dig_H1; 83 | int16_t dig_H2; 84 | uint16_t dig_H3; 85 | int16_t dig_H4; 86 | int16_t dig_H5; 87 | int8_t dig_H6; 88 | } bme280_calib_data_t; 89 | bme280_calib_data_t Calib_data; 90 | 91 | 92 | /////////////////////////////////////////////////////////////////////////////// 93 | int bme280_read(const uint8_t Register__u8, uint8_t * Data__u8p, uint8_t Num_bytes__u8) 94 | { 95 | if (Chip_enable_selected__i == -1) { return 0; } 96 | if (Num_bytes__u8 >= SENSOR_MODULE_MAX_XFER_LEN) { return 0; } 97 | 98 | uint8_t Buffer__u8a[SENSOR_MODULE_MAX_XFER_LEN]; 99 | memset(Buffer__u8a, 0, SENSOR_MODULE_MAX_XFER_LEN); 100 | 101 | // Set bit 7 high to tell it to read. 102 | Buffer__u8a[0] = (0x80 | Register__u8); 103 | int Result__i = 104 | wiringPiSPIDataRW(Chip_enable_selected__i, Buffer__u8a, Num_bytes__u8 + 1); 105 | int Out_idx__i = 0; 106 | while (Out_idx__i < (Result__i - 1)) 107 | { 108 | Data__u8p[Out_idx__i] = Buffer__u8a[Out_idx__i + 1]; 109 | Out_idx__i++; 110 | } 111 | 112 | return Result__i - 1; 113 | } 114 | 115 | /////////////////////////////////////////////////////////////////////////////// 116 | int bme280_write(const uint8_t Register__u8, const uint8_t * Data__u8p, uint8_t Num_bytes__u8) 117 | { 118 | if (Chip_enable_selected__i == -1) { return 0; } 119 | if (Num_bytes__u8 > SENSOR_MODULE_MAX_XFER_LEN) { return 0; } 120 | 121 | uint8_t Buffer__u8a[SENSOR_MODULE_MAX_XFER_LEN]; 122 | 123 | uint8_t Write_idx__u8 = 0; 124 | while (Write_idx__u8 < Num_bytes__u8) 125 | { 126 | // Set bit 7 low to tell it to write. 127 | Buffer__u8a[Write_idx__u8 * 2] = (0x7F & (Register__u8 + Write_idx__u8)); 128 | Buffer__u8a[Write_idx__u8 * 2 + 1] = *Data__u8p; 129 | 130 | Write_idx__u8++; 131 | Data__u8p++; 132 | } 133 | 134 | int Result__i = wiringPiSPIDataRW(Chip_enable_selected__i, 135 | Buffer__u8a, Num_bytes__u8 * 2); 136 | 137 | return Result__i / 2; 138 | } 139 | 140 | /////////////////////////////////////////////////////////////////////////////// 141 | int bme280_init(int Chip_enable_to_use__i) 142 | { 143 | #ifdef SHOW_DEBUG_OUTPUT 144 | printf("bme280_init(%i)\n", Chip_enable_to_use__i); 145 | #endif 146 | 147 | if ((Chip_enable_to_use__i < 0) || (Chip_enable_to_use__i > 1)) 148 | { 149 | return 0; 150 | } 151 | Chip_enable_selected__i = Chip_enable_to_use__i; 152 | 153 | // Verify that the chip is really a BME280. 154 | uint8_t ID_value__u8 = 0; 155 | int Bytes_read__i = bme280_read(eBME280reg_CHIPID, &ID_value__u8, 1); 156 | if (Bytes_read__i != 1) 157 | { 158 | return 0; 159 | } 160 | #ifdef SHOW_DEBUG_OUTPUT 161 | printf("Read 0x%02x from register 0x%02x\n", ID_value__u8, eBME280reg_CHIPID); 162 | #endif 163 | 164 | if (ID_value__u8 != 0x60) 165 | { 166 | #ifdef SHOW_DEBUG_OUTPUT 167 | printf("This is not a BME280. Expecting an ID register value of 0x%02x\n", 168 | 0x60); 169 | #endif 170 | return 0; 171 | } 172 | 173 | #define T_P_CALIB_NUM_BYTES (24) 174 | Bytes_read__i = bme280_read(eBME280reg_DIG_T1, (uint8_t *)&Calib_data, 175 | T_P_CALIB_NUM_BYTES); 176 | if (Bytes_read__i != T_P_CALIB_NUM_BYTES) 177 | { 178 | #ifdef SHOW_DEBUG_OUTPUT 179 | printf("Err: Only read %i out of %i calibration data bytes.\n", 180 | Bytes_read__i, T_P_CALIB_NUM_BYTES); 181 | #endif 182 | return 0; 183 | } 184 | uint8_t Hum_calib_buf__u8a[9]; 185 | Bytes_read__i += bme280_read(eBME280reg_DIG_H1, &Hum_calib_buf__u8a[0], 1); 186 | if (Bytes_read__i != T_P_CALIB_NUM_BYTES + 1) 187 | { 188 | #ifdef SHOW_DEBUG_OUTPUT 189 | printf("Err: Only read %i out of %i calibration data bytes.\n", 190 | Bytes_read__i, T_P_CALIB_NUM_BYTES + 1); 191 | #endif 192 | return 0; 193 | } 194 | Bytes_read__i += bme280_read(eBME280reg_DIG_H2, &Hum_calib_buf__u8a[1], 7); 195 | if (Bytes_read__i != T_P_CALIB_NUM_BYTES + 8) 196 | { 197 | #ifdef SHOW_DEBUG_OUTPUT 198 | printf("Err: Only read %i out of %i calibration data bytes.\n", 199 | Bytes_read__i, T_P_CALIB_NUM_BYTES + 8); 200 | #endif 201 | return 0; 202 | } 203 | #ifdef SHOW_DEBUG_OUTPUT 204 | printf("Read %i calibration data bytes starting at 0x%02x.\n", 205 | Bytes_read__i, eBME280reg_DIG_T1); 206 | #endif 207 | 208 | // Decode the humidity compensation constants. 209 | Calib_data.dig_H1 = Hum_calib_buf__u8a[0]; 210 | Calib_data.dig_H2 = (int16_t)(((uint16_t)Hum_calib_buf__u8a[1]) 211 | + (((uint16_t)Hum_calib_buf__u8a[2]) << 8)); 212 | Calib_data.dig_H3 = Hum_calib_buf__u8a[3]; 213 | Calib_data.dig_H4 = (int16_t)((((uint16_t)Hum_calib_buf__u8a[4]) << 4) 214 | + (((uint16_t)Hum_calib_buf__u8a[5]) & 0x0F)); 215 | Calib_data.dig_H5 = (int16_t)((((uint16_t)Hum_calib_buf__u8a[5]) >> 4) 216 | + (((uint16_t)Hum_calib_buf__u8a[6]) << 4)); 217 | Calib_data.dig_H6 = (int8_t)Hum_calib_buf__u8a[7]; 218 | 219 | // bits 7~5 = 001 = temperature oversampling * 1 220 | // bits 4~2 = 111 = pressure oversampling * 16 221 | // bits 1~0 = 11 = normal power mode 222 | const uint8_t Control_setting__u8 = 0x3F; 223 | uint8_t Bytes_written__u8 = bme280_write(eBME280reg_CONTROL, 224 | &Control_setting__u8, 1); 225 | if (Bytes_written__u8 != 1) 226 | { 227 | #ifdef SHOW_DEBUG_OUTPUT 228 | printf("Err: Could not write 0x%02x to register 0x%02x.\n", 229 | Control_setting__u8, eBME280reg_CONTROL); 230 | #endif 231 | return 0; 232 | } 233 | #ifdef SHOW_DEBUG_OUTPUT 234 | printf("Wrote 0x%02x to configuration register 0x%02x.\n", 235 | Control_setting__u8, eBME280reg_CONTROL); 236 | #endif 237 | 238 | return 1; 239 | } 240 | 241 | /////////////////////////////////////////////////////////////////////////////// 242 | // Returns temperature in DegC, resolution is 0.01 DegC. 243 | // For example: Output value of “5123” equals 51.23 DegC. 244 | // t_fine is stored globally since it is also used by the pressure comp calc. 245 | // Note: Must call this before calling compensate_P or compensate_H because of 246 | // the global t_fine variable. 247 | int32_t t_fine = 0; 248 | int32_t bme280_compensate_T_int32(int32_t adc_T) 249 | { 250 | int32_t var1, var2, T; 251 | var1 = ((((adc_T >> 3) - ((int32_t)Calib_data.dig_T1 << 1))) 252 | * ((int32_t)Calib_data.dig_T2)) >> 11; 253 | var2 = (((((adc_T >> 4) - ((int32_t)Calib_data.dig_T1)) 254 | * ((adc_T >> 4) - ((int32_t)Calib_data.dig_T1))) >> 12) 255 | * ((int32_t)Calib_data.dig_T3)) >> 14; 256 | t_fine = var1 + var2; 257 | T = (t_fine * 5 + 128) >> 8; 258 | return T; 259 | } 260 | 261 | /////////////////////////////////////////////////////////////////////////////// 262 | // Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 263 | // integer bits and 8 fractional bits). 264 | // For example: Output value of “24674867” represents 24674867/256 = 96386.2 Pa 265 | // = 963.862 hPa 266 | // Note: Must call compensate_T before calling this because of 267 | // the global t_fine variable. 268 | uint32_t bme280_compensate_P_int64(int32_t adc_P) 269 | { 270 | int64_t var1, var2, p; 271 | var1 = ((int64_t)t_fine) - 128000LL; 272 | var2 = var1 * var1 * (int64_t)Calib_data.dig_P6; 273 | var2 = var2 + ((var1*(int64_t)Calib_data.dig_P5) << 17); 274 | var2 = var2 + (((int64_t)Calib_data.dig_P4) << 35); 275 | var1 = ((var1 * var1 * (int64_t)Calib_data.dig_P3)>>8) + ((var1 * (int64_t)Calib_data.dig_P2) << 12); 276 | var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)Calib_data.dig_P1) >> 33; 277 | if (var1 == 0) 278 | { 279 | // Avoid divide by zero exception. 280 | return 0; 281 | } 282 | p = 1048576 - adc_P; 283 | p = (((p << 31) - var2) * 3125) / var1; 284 | var1 = (((int64_t)Calib_data.dig_P9) * (p >> 13) * (p >> 13)) >> 25; 285 | var2 = (((int64_t)Calib_data.dig_P8) * p) >> 19; 286 | p = ((p + var1 + var2) >> 8) + (((int64_t)Calib_data.dig_P7) << 4); 287 | return (uint32_t)p; 288 | } 289 | 290 | /////////////////////////////////////////////////////////////////////////////// 291 | // Returns humidity as a relative percentage. 292 | // Encoded as Q22.10 format (22 integer bits and 10 fractional bits). 293 | // For example: Output value of “47445” represents 47445/1024 = 46.333 %RH 294 | // Note: Must call compensate_T before calling this because of 295 | // the global t_fine variable. 296 | uint32_t bme280_compensate_H_int32(int32_t adc_H) 297 | { 298 | int32_t v_x1_u32r; 299 | v_x1_u32r = (t_fine - ((int32_t)76800L)); 300 | v_x1_u32r = (((((adc_H << 14) - (((int32_t)Calib_data.dig_H4) << 20) 301 | - (((int32_t)Calib_data.dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) 302 | * (((((((v_x1_u32r * ((int32_t)Calib_data.dig_H6)) >> 10) 303 | * (((v_x1_u32r * ((int32_t)Calib_data.dig_H3)) >> 11) 304 | + ((int32_t)32768))) >> 10) + ((int32_t)2097152)) 305 | * ((int32_t)Calib_data.dig_H2) + 8192) >> 14)); 306 | v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) 307 | * ((int32_t)Calib_data.dig_H1)) >> 4)); 308 | v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r); 309 | v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r); 310 | return (uint32_t)(v_x1_u32r >> 12); 311 | } 312 | 313 | /////////////////////////////////////////////////////////////////////////////// 314 | int bme280_read_sensors(float * Temp_c__fp, float * Pres_Pa__fp, 315 | float * Hum_pct__fp) 316 | { 317 | int Return_status__i = 0; 318 | 319 | // Make sure the sensor isn't busy updating values. 320 | uint8_t Status__u8 = 0x01; 321 | while ((Status__u8 & 0x01) != 0) 322 | { 323 | uint8_t Num_bytes_read__u8 = bme280_read(eBME280reg_STATUS, &Status__u8, 1); 324 | if (Num_bytes_read__u8 != 1) 325 | { 326 | printf("failed to read 8 bytes\r\n"); 327 | return Return_status__i; 328 | } 329 | } 330 | 331 | const uint8_t Num_bytes_to_read__u8 = 8; 332 | uint8_t Buffer__u8a[Num_bytes_to_read__u8]; 333 | int Num_retries__i = 0; 334 | while (Num_retries__i <= Num_allowed_retries__i) 335 | { 336 | uint8_t Register__u8 = eBME280reg_PRESDATA; 337 | int Num_bytes_read__i = bme280_read(Register__u8, Buffer__u8a, 338 | Num_bytes_to_read__u8); 339 | if (Num_bytes_read__i == (int)Num_bytes_to_read__u8) 340 | { 341 | // Decode the fields. 342 | 343 | // Pressure is in registers 0xf7 ~ 0xf9. 344 | // Most Significant Bits [19:12] of Pressure ADC value. 345 | int32_t Pressure_raw_adc__i32 = ((int32_t)Buffer__u8a[0]) << 12; 346 | // Mid/lower Significant Bits [11:4] of Pressure ADC value. 347 | Pressure_raw_adc__i32 += ((int32_t)Buffer__u8a[1]) << 4; 348 | // Least Significant Bits [3]|[3:2]|[3:1]|[3:0], depending on the 349 | // resolution as determined by the oversampling setting. 350 | Pressure_raw_adc__i32 += ((int32_t)Buffer__u8a[2]) & 0x04; 351 | 352 | // Temperature is in registers 0xfa ~ 0xfc. 353 | // Most Significant Bits [19:12] of Temperature ADC value. 354 | int32_t Temperature_raw_adc__i32 = ((int32_t)Buffer__u8a[3]) << 12; 355 | // Mid/lower Significant Bits [11:4] of Temperature ADC value. 356 | Temperature_raw_adc__i32 += ((int32_t)Buffer__u8a[4]) << 4; 357 | // Least Significant Bits [3]|[3:2]|[3:1]|[3:0], depending on the 358 | // resolution as determined by the oversampling setting. 359 | Temperature_raw_adc__i32 += ((int32_t)Buffer__u8a[5]) & 0x04; 360 | 361 | // Humidity is in registers 0xfd ~ 0xfe. 362 | // Most Significant Bits [15:8] of Humidity ADC value. 363 | int32_t Humidity_raw_adc__i32 = (((int32_t)Buffer__u8a[6]) << 8); 364 | // Least Significant Bits [7:0] of Humidity ADC value. 365 | Humidity_raw_adc__i32 += ((int32_t)Buffer__u8a[7]); 366 | 367 | *Temp_c__fp = bme280_compensate_T_int32(Temperature_raw_adc__i32) / 100.0; 368 | *Pres_Pa__fp = bme280_compensate_P_int64(Pressure_raw_adc__i32) / 256.0; 369 | *Hum_pct__fp = bme280_compensate_H_int32(Humidity_raw_adc__i32) / 1024.0; 370 | 371 | Return_status__i = 1; 372 | break; 373 | } 374 | 375 | Num_retries__i++; 376 | delay(1); 377 | } 378 | 379 | return Return_status__i; 380 | } 381 | -------------------------------------------------------------------------------- /bme280.h: -------------------------------------------------------------------------------- 1 | /* 2 | * IoT Hub Raspberry Pi C - Microsoft Sample Code - Copyright (c) 2017 - Licensed MIT 3 | */ 4 | 5 | /////////////////////////////////////////////////////////////////////////////// 6 | // 7 | // bme280.h: 8 | // SPI based interface to read temperature, pressure and humidity samples from 9 | // a BME280 module. 10 | // 11 | /////////////////////////////////////////////////////////////////////////////// 12 | 13 | #ifndef BME280_H_ 14 | #define BME280_H_ 15 | 16 | 17 | /////////////////////////////////////////////////////////////////////////////// 18 | // Call this after setting the chip select (or SPI Enable) pin (via 19 | // bme280_set_cs_pin()), and before calling the bmp280_read function. 20 | // Return: 0 if the module was not found. 21 | // 1 if the module was readable, and verified to be a BMP280, and the 22 | // calibration data was read. 23 | int bme280_init(int Chip_enable_to_use__i); 24 | 25 | /////////////////////////////////////////////////////////////////////////////// 26 | // Prerequisite: 27 | // You must call wiringPiSetup before calling this function. For example: 28 | // int Result__i = wiringPiSetup(); 29 | // if (Result__i != 0) exit(Result__i); 30 | // You must call wiringPiSPISetup before calling this function. For example: 31 | // int Spi_fd__i = wiringPiSPISetup(Spi_channel__i, Spi_clock__i); 32 | // if (Spi_fd__i < 0) 33 | // { 34 | // printf("Can't setup SPI, error %i calling wiringPiSPISetup(%i, %i) %s\n", 35 | // Spi_fd__i, Spi_channel__i, Spi_clock__i, strerror(Spi_fd__i)); 36 | // exit(Spi_fd__i); 37 | // } 38 | // 39 | // Param: Temp_C__fp Pointer to a float to receive the current temperature in 40 | // degrees Celcius. Only set if read is successful. 41 | // Param: Pres_Pa__fp Pointer to a float to receive the current pressure 42 | // as hPa. Only set if read is successful. 43 | // Param: Hum_pct__fp Pointer to a float to receive the current humidity 44 | // as a percentage. Only set if read is successful. 45 | // Return: If wiringPi gets an error, this will be < 0 46 | // If the read attempts fail, this will be 1 47 | // If the read succeeds within the available retries, returns 0 48 | int bme280_read_sensors(float * Temp_C__fp, float * Pres_Pa__fp, float * Hum_pct__fp); 49 | 50 | #endif // BME280_H_ 51 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * IoT Hub Raspberry Pi C - Microsoft Sample Code - Copyright (c) 2017 - Licensed MIT 3 | */ 4 | 5 | #ifndef CONFIG_H_ 6 | #define CONFIG_H_ 7 | 8 | #define INTERVAL 2000 9 | #define SIMULATED_DATA 0 10 | #define BUFFER_SIZE 256 11 | 12 | #define LED_PIN 7 13 | 14 | #define CREDENTIAL_PATH "~/.iot-hub" 15 | 16 | #endif // CONFIG_H_ 17 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * IoT Hub Raspberry Pi C - Microsoft Sample Code - Copyright (c) 2017 - Licensed MIT 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "./config.h" 20 | #include "./wiring.h" 21 | #include "./telemetry.h" 22 | 23 | const char *onSuccess = "\"Successfully invoke device method\""; 24 | const char *notFound = "\"No method found\""; 25 | 26 | static bool messagePending = false; 27 | static bool sendingMessage = true; 28 | 29 | static int interval = INTERVAL; 30 | 31 | static const char *EVENT_SUCCESS = "success"; 32 | static const char *EVENT_FAILED = "failed"; 33 | 34 | pthread_t thread; 35 | 36 | static void sendCallback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void *userContextCallback) 37 | { 38 | if (IOTHUB_CLIENT_CONFIRMATION_OK == result) 39 | { 40 | blinkLED(); 41 | } 42 | else 43 | { 44 | LogError("Failed to send message to Azure IoT Hub"); 45 | } 46 | 47 | messagePending = false; 48 | } 49 | 50 | static void sendMessages(IOTHUB_CLIENT_LL_HANDLE iotHubClientHandle, char *buffer, int temperatureAlert) 51 | { 52 | IOTHUB_MESSAGE_HANDLE messageHandle = IoTHubMessage_CreateFromByteArray(buffer, strlen(buffer)); 53 | if (messageHandle == NULL) 54 | { 55 | LogError("Unable to create a new IoTHubMessage"); 56 | } 57 | else 58 | { 59 | MAP_HANDLE properties = IoTHubMessage_Properties(messageHandle); 60 | Map_Add(properties, "temperatureAlert", (temperatureAlert > 0) ? "true" : "false"); 61 | LogInfo("Sending message: %s", buffer); 62 | if (IoTHubClient_LL_SendEventAsync(iotHubClientHandle, messageHandle, sendCallback, NULL) != IOTHUB_CLIENT_OK) 63 | { 64 | LogError("Failed to send message to Azure IoT Hub"); 65 | } 66 | else 67 | { 68 | messagePending = true; 69 | LogInfo("Message sent to Azure IoT Hub"); 70 | } 71 | 72 | IoTHubMessage_Destroy(messageHandle); 73 | } 74 | } 75 | 76 | static char *get_device_id(char *str) 77 | { 78 | char *substr = strstr(str, "DeviceId="); 79 | 80 | if (substr == NULL) 81 | return NULL; 82 | 83 | // skip "DeviceId=" 84 | substr += 9; 85 | 86 | char *semicolon = strstr(substr, ";"); 87 | int length = semicolon == NULL ? strlen(substr) : semicolon - substr; 88 | char *device_id = calloc(1, length + 1); 89 | memcpy(device_id, substr, length); 90 | device_id[length] = '\0'; 91 | 92 | return device_id; 93 | } 94 | 95 | static void start() 96 | { 97 | sendingMessage = true; 98 | } 99 | 100 | static void stop() 101 | { 102 | sendingMessage = false; 103 | } 104 | 105 | int deviceMethodCallback( 106 | const char *methodName, 107 | const unsigned char *payload, 108 | size_t size, 109 | unsigned char **response, 110 | size_t *response_size, 111 | void *userContextCallback) 112 | { 113 | LogInfo("Try to invoke method %s\r\n", methodName); 114 | const char *responseMessage = onSuccess; 115 | int result = 200; 116 | 117 | if (strcmp(methodName, "start") == 0) 118 | { 119 | start(); 120 | } 121 | else if (strcmp(methodName, "stop") == 0) 122 | { 123 | stop(); 124 | } 125 | else 126 | { 127 | LogError("No method %s found\r\n", methodName); 128 | responseMessage = notFound; 129 | result = 404; 130 | } 131 | 132 | *response_size = strlen(responseMessage); 133 | *response = (unsigned char *)malloc(*response_size); 134 | strncpy((char *)(*response), responseMessage, *response_size); 135 | 136 | return result; 137 | } 138 | 139 | void twinCallback( 140 | DEVICE_TWIN_UPDATE_STATE updateState, 141 | const unsigned char *payLoad, 142 | size_t size, 143 | void *userContextCallback) 144 | { 145 | char *temp = (char *)malloc(size + 1); 146 | for (int i = 0; i < size; i++) 147 | { 148 | temp[i] = (char)(payLoad[i]); 149 | } 150 | temp[size] = '\0'; 151 | MULTITREE_HANDLE tree = NULL; 152 | 153 | if (JSON_DECODER_OK == JSONDecoder_JSON_To_MultiTree(temp, &tree)) 154 | { 155 | MULTITREE_HANDLE child = NULL; 156 | 157 | if (MULTITREE_OK != MultiTree_GetChildByName(tree, "desired", &child)) 158 | { 159 | LogInfo("This device twin message contains desired message only"); 160 | child = tree; 161 | } 162 | const void *value = NULL; 163 | if (MULTITREE_OK == MultiTree_GetLeafValue(child, "interval", &value)) 164 | { 165 | interval = atoi((const char *)value); 166 | } 167 | } 168 | MultiTree_Destroy(tree); 169 | free(temp); 170 | } 171 | 172 | IOTHUBMESSAGE_DISPOSITION_RESULT receiveMessageCallback(IOTHUB_MESSAGE_HANDLE message, void *userContextCallback) 173 | { 174 | const unsigned char *buffer = NULL; 175 | size_t size = 0; 176 | 177 | if (IOTHUB_MESSAGE_OK != IoTHubMessage_GetByteArray(message, &buffer, &size)) 178 | { 179 | return IOTHUBMESSAGE_ABANDONED; 180 | } 181 | 182 | // message needs to be converted to zero terminated string 183 | char *temp = (char *)malloc(size + 1); 184 | 185 | if (temp == NULL) 186 | { 187 | return IOTHUBMESSAGE_ABANDONED; 188 | } 189 | 190 | strncpy(temp, buffer, size); 191 | temp[size] = '\0'; 192 | 193 | (void)printf("Receiving message: %s\r\n", temp); 194 | free(temp); 195 | 196 | return IOTHUBMESSAGE_ACCEPTED; 197 | } 198 | 199 | static char *readFile(char *fileName) 200 | { 201 | FILE *fp; 202 | int size; 203 | char *buffer; 204 | 205 | fp = fopen(fileName, "rb"); 206 | 207 | if (fp == NULL) 208 | { 209 | LogError("ERROR: File %s doesn't exist!", fileName); 210 | return NULL; 211 | } 212 | 213 | fseek(fp, 0L, SEEK_END); 214 | size = ftell(fp); 215 | rewind(fp); 216 | 217 | // Allocate memory for entire content 218 | buffer = calloc(1, size + 1); 219 | 220 | if (buffer == NULL) 221 | { 222 | fclose(fp); 223 | LogError("ERROR: Failed to allocate memory."); 224 | return NULL; 225 | } 226 | 227 | // Read the file into the buffer 228 | if (1 != fread(buffer, size, 1, fp)) 229 | { 230 | fclose(fp); 231 | free(buffer); 232 | LogError("ERROR: Failed to read the file %s into memory.", fileName); 233 | return NULL; 234 | } 235 | 236 | fclose(fp); 237 | 238 | return buffer; 239 | } 240 | 241 | static bool setX509Certificate(IOTHUB_CLIENT_LL_HANDLE iotHubClientHandle, char *deviceId) 242 | { 243 | char certName[256]; 244 | char keyName[256]; 245 | char cwd[1024]; 246 | 247 | snprintf(certName, sizeof(certName), "%s/%s-cert.pem", CREDENTIAL_PATH, deviceId); 248 | snprintf(keyName, sizeof(keyName), "%s/%s-key.pem", CREDENTIAL_PATH, deviceId); 249 | 250 | char *x509certificate = readFile(certName); 251 | char *x509privatekey = readFile(keyName); 252 | 253 | if (x509certificate == NULL || 254 | x509privatekey == NULL || 255 | IoTHubClient_LL_SetOption(iotHubClientHandle, OPTION_X509_CERT, x509certificate) != IOTHUB_CLIENT_OK || 256 | IoTHubClient_LL_SetOption(iotHubClientHandle, OPTION_X509_PRIVATE_KEY, x509privatekey) != IOTHUB_CLIENT_OK) 257 | { 258 | LogError("Failed to set options for x509."); 259 | return false; 260 | } 261 | 262 | free(x509certificate); 263 | free(x509privatekey); 264 | 265 | return true; 266 | } 267 | 268 | char *parse_iothub_name(char *connectionString) 269 | { 270 | if (connectionString == NULL) 271 | { 272 | return NULL; 273 | } 274 | 275 | char *hostName = strtok(connectionString, "."); 276 | int prefixLen = strlen("HostName="); 277 | int len = strlen(hostName) - prefixLen + 1; 278 | char *iotHubName = (char *)malloc(len); 279 | if (iotHubName == NULL) 280 | { 281 | return NULL; 282 | } 283 | memcpy(iotHubName, hostName + prefixLen, len - 1); 284 | iotHubName[len - 1] = '\0'; 285 | return iotHubName; 286 | } 287 | 288 | typedef struct AIParams 289 | { 290 | char *iotHubName; 291 | const char *event; 292 | const char *message; 293 | } AIParams; 294 | 295 | void *send_ai(void *argv) 296 | { 297 | AIParams *params = argv; 298 | send_telemetry_data(params->iotHubName, params->event, params->message); 299 | free(params->iotHubName); 300 | free(params); 301 | } 302 | 303 | void *send_telemetry_data_multi_thread(char *iotHubName, const char *eventName, const char *message) 304 | { 305 | AIParams *params = malloc(sizeof(AIParams)); 306 | if (params != NULL) 307 | { 308 | params->iotHubName = iotHubName; 309 | params->event = eventName; 310 | params->message = message; 311 | pthread_create(&thread, NULL, send_ai, (void *)params); 312 | } 313 | else 314 | { 315 | free(iotHubName); 316 | } 317 | } 318 | 319 | int main(int argc, char *argv[]) 320 | { 321 | initial_telemetry(); 322 | if (argc < 2) 323 | { 324 | LogError("Usage: %s ", argv[0]); 325 | send_telemetry_data(NULL, EVENT_FAILED, "Device connection string is not provided"); 326 | return 1; 327 | } 328 | 329 | setupWiring(); 330 | 331 | char device_id[257]; 332 | char *device_id_src = get_device_id(argv[1]); 333 | 334 | if (device_id_src == NULL) 335 | { 336 | LogError("Cannot parse device id from IoT device connection string"); 337 | send_telemetry_data(NULL, EVENT_FAILED, "Cannot parse device id from connection string"); 338 | pthread_join(thread, NULL); 339 | return 1; 340 | } 341 | 342 | snprintf(device_id, sizeof(device_id), "%s", device_id_src); 343 | free(device_id_src); 344 | 345 | IOTHUB_CLIENT_LL_HANDLE iotHubClientHandle; 346 | 347 | if (platform_init() != 0) 348 | { 349 | LogError("Failed to initialize the platform."); 350 | send_telemetry_data(NULL, EVENT_FAILED, "Failed to initialize the platform."); 351 | } 352 | else 353 | { 354 | if ((iotHubClientHandle = IoTHubClient_LL_CreateFromConnectionString(argv[1], MQTT_Protocol)) == NULL) 355 | { 356 | LogError("iotHubClientHandle is NULL!"); 357 | send_telemetry_data(NULL, EVENT_FAILED, "Cannot create iotHubClientHandle"); 358 | } 359 | else 360 | { 361 | if (strstr(argv[1], "x509=true") != NULL) 362 | { 363 | // Use X.509 certificate authentication. 364 | if (!setX509Certificate(iotHubClientHandle, device_id)) 365 | { 366 | send_telemetry_data(NULL, EVENT_FAILED, "Certificate is not right"); 367 | return 1; 368 | } 369 | } 370 | 371 | // set C2D and device method callback 372 | IoTHubClient_LL_SetMessageCallback(iotHubClientHandle, receiveMessageCallback, NULL); 373 | IoTHubClient_LL_SetDeviceMethodCallback(iotHubClientHandle, deviceMethodCallback, NULL); 374 | IoTHubClient_LL_SetDeviceTwinCallback(iotHubClientHandle, twinCallback, NULL); 375 | 376 | IoTHubClient_LL_SetOption(iotHubClientHandle, "product_info", "HappyPath_RaspberryPi-C"); 377 | 378 | char *iotHubName = parse_iothub_name(argv[1]); 379 | send_telemetry_data_multi_thread(iotHubName, EVENT_SUCCESS, "IoT hub connection is established"); 380 | int count = 0; 381 | while (true) 382 | { 383 | if (sendingMessage && !messagePending) 384 | { 385 | ++count; 386 | char buffer[BUFFER_SIZE]; 387 | if (buffer != NULL) 388 | { 389 | int result = readMessage(count, buffer); 390 | if (result != -1) 391 | { 392 | sendMessages(iotHubClientHandle, buffer, result); 393 | } 394 | else 395 | { 396 | LogError("Failed to read message"); 397 | } 398 | } 399 | delay(interval); 400 | } 401 | IoTHubClient_LL_DoWork(iotHubClientHandle); 402 | } 403 | 404 | IoTHubClient_LL_Destroy(iotHubClientHandle); 405 | } 406 | platform_deinit(); 407 | } 408 | 409 | return 0; 410 | } 411 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sh -c ' 3 | sudo apt-get update 4 | sudo apt-get install -y libssl-dev git cmake build-essential curl libcurl4-openssl-dev uuid-dev 5 | 6 | #On Raspbian Stretch precompiled sdk from ppa is no good. Lets compile it manually 7 | cd ~ 8 | mkdir Source 9 | cd Source 10 | git clone --recursive https://github.com/azure/azure-iot-sdk-c.git 11 | 12 | cd azure-iot-sdk-c/build_all/linux 13 | ./build.sh --no-make 14 | 15 | cd ../../cmake/iotsdk_linux 16 | make 17 | 18 | sudo make install 19 | ' 20 | 21 | vercomp() { 22 | if [[ $1 == $2 ]] 23 | then 24 | return 0 25 | fi 26 | local IFS=. 27 | local i ver1=($1) ver2=($2) 28 | # fill empty fields in ver1 with zeros 29 | for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)) 30 | do 31 | ver1[i]=0 32 | done 33 | for ((i=0; i<${#ver1[@]}; i++)) 34 | do 35 | if [[ -z ${ver2[i]} ]] 36 | then 37 | # fill empty fields in ver2 with zeros 38 | ver2[i]=0 39 | fi 40 | if ((10#${ver1[i]} > 10#${ver2[i]})) 41 | then 42 | return 1 43 | fi 44 | if ((10#${ver1[i]} < 10#${ver2[i]})) 45 | then 46 | return 2 47 | fi 48 | done 49 | return 0 50 | } 51 | 52 | CMAKE_LEAST="2.8.12" 53 | GCC_LEAST="4.4.7" 54 | GCC_VER=`gcc --version | head -n1 | cut -d" " -f4` 55 | CMAKE_VER=`cmake --version | head -n1 | cut -d" " -f3` 56 | vercomp $GCC_VER $GCC_LEAST 57 | if [ $? -eq 2 ] 58 | then 59 | echo "gcc version too low (current:$GCC_VER,require:$GCC_LEAST)" 60 | else 61 | echo "gcc version check pass (current:$GCC_VER,require:$GCC_LEAST)" 62 | fi 63 | 64 | vercomp $CMAKE_VER $CMAKE_LEAST 65 | if [ $? -eq 2 ] 66 | then 67 | echo "cmake version too low (current:$CMAKE_VER,require:$CMAKE_LEAST)" 68 | else 69 | echo "cmake version check pass (current:$CMAKE_VER,require:$CMAKE_LEAST)" 70 | fi 71 | 72 | git clone https://github.com/WiringPi/WiringPi 73 | cd ./WiringPi 74 | ./build 75 | cd .. 76 | 77 | git clone https://github.com/kgabis/parson.git 78 | cd ./parson 79 | mv parson.c parson.h .. 80 | cd .. 81 | rm -rf parson 82 | 83 | if [[ $1 == "--simulated-data" ]]; then 84 | echo -e "Using simulated data" 85 | sed -i 's/#define SIMULATED_DATA 0/#define SIMULATED_DATA 1/' config.h 86 | fi 87 | 88 | cmake . && make 89 | 90 | -------------------------------------------------------------------------------- /telemetry.c: -------------------------------------------------------------------------------- 1 | /* 2 | * IoT Hub Raspberry Pi C - Microsoft Sample Code - Copyright (c) 2017 - Licensed MIT 3 | */ 4 | #include "./telemetry.h" 5 | 6 | static struct utsname platform; 7 | static const char *PATH = "https://dc.services.visualstudio.com/v2/track"; 8 | static const char *IKEY = "0823bae8-a3b8-4fd5-80e5-f7272a2377a9"; 9 | static const char *EVENT = "AIEVENT"; 10 | static const char *LANGUAGE = "C"; 11 | static const char *DEVICE = "RaspberryPi"; 12 | static const char *MCU = "STM32F412"; 13 | static const char *MAC_TEMPLATE = "%02X-%02X-%02X-%02X-%02X-%02X"; 14 | static const int HASH_LEN = 65; 15 | static const char *BODY_TEMPLATE_FULL = 16 | "{" 17 | "\"data\": {" 18 | "\"baseType\": \"EventData\"," 19 | "\"baseData\": {" 20 | "\"properties\": {" 21 | "\"language\": \"%s\"," 22 | "\"device\": \"%s\"," 23 | "\"mcu\": \"%s\"," 24 | "\"message\": \"%s\"," 25 | "\"mac\": \"%s\"," 26 | "\"iothub\": \"%s\"," 27 | "\"osType\": \"%s\"," 28 | "\"osPlatform\": \"%s\"," 29 | "\"osRelease\": \"%s\"" 30 | "}," 31 | "\"name\": \"%s\"" 32 | "}" 33 | "}," 34 | "\"time\": \"%s\"," 35 | "\"name\": \"%s\"," 36 | "\"iKey\": \"%s\"" 37 | "}"; 38 | 39 | static const char *BODY_TEMPLATE_MIN = 40 | "{" 41 | "\"data\": {" 42 | "\"baseType\": \"EventData\"," 43 | "\"baseData\": {" 44 | "\"properties\": {" 45 | "\"language\": \"C\"," 46 | "\"device\": \"RaspberryPi\"" 47 | "}," 48 | "\"name\": \"%s\"" 49 | "}" 50 | "}," 51 | "\"time\": \"%s\"," 52 | "\"name\": \"%s\"," 53 | "\"iKey\": \"%s\"," 54 | "\"tags\": {" 55 | "\"ai.location.ip\": \"0.0.0.0\"" 56 | "}" 57 | "}"; 58 | 59 | static const char *PROMPT_TEXT = "\nMicrosoft would like to collect data about how users use Azure IoT " \ 60 | "samples and some problems they encounter. Microsoft uses this information to improve "\ 61 | "our tooling experience. Participation is voluntary and when you choose to participate " \ 62 | "your device automatically sends information to Microsoft about how you use Azure IoT "\ 63 | "samples. If you want to change this setting after first time, please delete the "\ 64 | "telemetry.config file and restart the program. "\ 65 | "\n\nSelect y to enable data collection (y/n, default is y). "; 66 | 67 | static int enabled = 0; 68 | 69 | void initial_telemetry() 70 | { 71 | FILE *file; 72 | file = fopen("telemetry.config", "r"); 73 | if (file != NULL) 74 | { 75 | char config = (char)fgetc(file); 76 | enabled = config == 'y'; 77 | fclose(file); 78 | } 79 | else 80 | { 81 | printf("%s", PROMPT_TEXT); 82 | char answer = getchar(); 83 | file = fopen("telemetry.config", "w"); 84 | if (answer == 'n') 85 | { 86 | fputc('n', file); 87 | send_telemetry_data_without_sensitive_info("no"); 88 | enabled = 0; 89 | } 90 | else 91 | { 92 | fputc('y', file); 93 | send_telemetry_data_without_sensitive_info("yes"); 94 | enabled = 1; 95 | } 96 | fclose(file); 97 | } 98 | } 99 | 100 | void get_mac_address_hash(char outputBuffer[], int len) 101 | { 102 | struct ifreq s; 103 | int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); 104 | 105 | snprintf(s.ifr_name, sizeof(s.ifr_name), "eth0"); 106 | if (0 == ioctl(fd, SIOCGIFHWADDR, &s)) 107 | { 108 | char mac[18]; 109 | snprintf(mac, sizeof(mac), MAC_TEMPLATE, (unsigned char)s.ifr_addr.sa_data[0], 110 | (unsigned char)s.ifr_addr.sa_data[1], (unsigned char)s.ifr_addr.sa_data[2], 111 | (unsigned char)s.ifr_addr.sa_data[3], (unsigned char)s.ifr_addr.sa_data[4], 112 | (unsigned char)s.ifr_addr.sa_data[5]); 113 | mac[17] = '\n'; 114 | 115 | sha256(mac, outputBuffer, len); 116 | } 117 | 118 | close(fd); 119 | } 120 | 121 | void sha256(const char *str, char outputBuffer[], int len) 122 | { 123 | if (str == NULL) 124 | { 125 | outputBuffer[0] = '\0'; 126 | return; 127 | } 128 | unsigned char hash[SHA256_DIGEST_LENGTH]; 129 | SHA256_CTX sha256; 130 | SHA256_Init(&sha256); 131 | SHA256_Update(&sha256, str, strlen(str)); 132 | SHA256_Final(hash, &sha256); 133 | int i = 0; 134 | for (i = 0; i < SHA256_DIGEST_LENGTH; i++) 135 | { 136 | snprintf(outputBuffer + (i * 2), len, "%02x", hash[i]); 137 | } 138 | 139 | outputBuffer[HASH_LEN - 1] = 0; 140 | } 141 | 142 | void exec_command(char command [], char result []) 143 | { 144 | FILE * pipe = popen(command, "r"); 145 | if (pipe == NULL) 146 | { 147 | result = "unknown"; 148 | return; 149 | } 150 | 151 | fgets(result, LINE_BUFSIZE, pipe); 152 | if (strlen(result) == 0) 153 | { 154 | result = "unknown"; 155 | } 156 | } 157 | 158 | int substr_count(const char * str, const char * substr) 159 | { 160 | const char *tmp = str; 161 | int count = 0; 162 | while (tmp = strstr(tmp, substr)) 163 | { 164 | count++; 165 | tmp += strlen(substr); 166 | } 167 | return count; 168 | } 169 | 170 | void send_telemetry_data_without_sensitive_info(const char *event) 171 | { 172 | CURL *curl; 173 | CURLcode res; 174 | 175 | curl_global_init(CURL_GLOBAL_ALL); 176 | curl = curl_easy_init(); 177 | if (curl) 178 | { 179 | curl_easy_setopt(curl, CURLOPT_URL, PATH); 180 | 181 | time_t t = time(NULL); 182 | char *cur_time = ctime(&t); 183 | int tlen = strlen(cur_time) - 1; 184 | cur_time[tlen] = 0; 185 | 186 | int str_placeholder_count = substr_count(BODY_TEMPLATE_MIN, "%s"); 187 | int size = strlen(BODY_TEMPLATE_MIN) + strlen(IKEY) - strlen("%s") * str_placeholder_count 188 | + strlen(EVENT) + strlen(event) + tlen + 1; 189 | 190 | char *data = (char *)malloc(size * sizeof(char)); 191 | if (data != NULL) 192 | { 193 | snprintf(data, size, BODY_TEMPLATE_MIN, event, cur_time, EVENT, IKEY); 194 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); 195 | // hide curl output 196 | FILE *devnull = fopen("/dev/null", "w+"); 197 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, devnull); 198 | 199 | res = curl_easy_perform(curl); 200 | fclose(devnull); 201 | 202 | curl_easy_cleanup(curl); 203 | 204 | free(data); 205 | } 206 | } 207 | } 208 | 209 | void send_telemetry_data(const char *iotHubName, const char *event, const char *message) 210 | { 211 | if (!enabled) 212 | { 213 | return; 214 | } 215 | 216 | CURL *curl; 217 | CURLcode res; 218 | 219 | curl_global_init(CURL_GLOBAL_ALL); 220 | curl = curl_easy_init(); 221 | if (curl) 222 | { 223 | curl_easy_setopt(curl, CURLOPT_URL, PATH); 224 | 225 | char hash_mac[HASH_LEN]; 226 | unsigned char hash_iothub_name[HASH_LEN]; 227 | get_mac_address_hash(hash_mac, sizeof(hash_mac)); 228 | sha256(iotHubName, hash_iothub_name, sizeof(hash_iothub_name)); 229 | 230 | time_t t = time(NULL); 231 | char *cur_time = ctime(&t); 232 | int tlen = strlen(cur_time) - 1; 233 | cur_time[tlen] = 0; 234 | 235 | uname(&platform); 236 | char os_release[LINE_BUFSIZE]; 237 | char os_platform[LINE_BUFSIZE]; 238 | const char prefix[] = "ID_LIKE="; 239 | 240 | exec_command("/usr/bin/lsb_release -r| grep -oP \'\\d\\.\\d\' | tr -d \'\\r\\n\'", os_release); 241 | exec_command("cat /etc/os-release | grep -oP \'^ID_LIKE=.*$\' | tr -d \'\\r\\n\'", os_platform); 242 | int str_placeholder_count = substr_count(BODY_TEMPLATE_FULL, "%s"); 243 | int platform_str_offset = strlen(prefix); 244 | int size = strlen(BODY_TEMPLATE_FULL) + strlen(LANGUAGE) + strlen(DEVICE) + strlen(MCU) + strlen(EVENT) 245 | + strlen(IKEY) - strlen("%s") * str_placeholder_count + strlen(hash_iothub_name) 246 | + strlen(hash_mac) + strlen(platform.sysname) + strlen(os_platform + platform_str_offset) 247 | + strlen(os_release) + strlen(message) + strlen(event) + tlen + 1; 248 | char *data = (char *)malloc(size * sizeof(char)); 249 | if (data != NULL) 250 | { 251 | snprintf(data, size, BODY_TEMPLATE_FULL, LANGUAGE, DEVICE, MCU, message, hash_mac, hash_iothub_name, 252 | platform.sysname, os_platform + platform_str_offset, os_release, event, cur_time, EVENT, IKEY); 253 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); 254 | // hide curl output 255 | FILE *devnull = fopen("/dev/null", "w+"); 256 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, devnull); 257 | 258 | res = curl_easy_perform(curl); 259 | fclose(devnull); 260 | 261 | curl_easy_cleanup(curl); 262 | 263 | free(data); 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /telemetry.h: -------------------------------------------------------------------------------- 1 | /* 2 | * IoT Hub Raspberry Pi C - Microsoft Sample Code - Copyright (c) 2017 - Licensed MIT 3 | */ 4 | #ifndef TELEMETRY_H_ 5 | #define TELEMETRY_H_ 6 | 7 | #define LINE_BUFSIZE 128 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | void send_telemetry_data(const char *iotHubName, const char *event, const char *message); 24 | void get_mac_address_hash(char outputBuffer[], int len); 25 | void sha256(const char *str, char outputBuffer[], int len); 26 | void exec_command(char command [], char result []); 27 | int substr_count(const char * str, const char * substr); 28 | void initial_telemetry(); 29 | void send_telemetry_data_without_sensitive_info(const char *event); 30 | 31 | #endif // TELEMETRY_H_ 32 | -------------------------------------------------------------------------------- /wiring.c: -------------------------------------------------------------------------------- 1 | /* 2 | * IoT Hub Raspberry Pi C - Microsoft Sample Code - Copyright (c) 2017 - Licensed MIT 3 | */ 4 | #include "./wiring.h" 5 | 6 | static unsigned int BMEInitMark = 0; 7 | 8 | #if SIMULATED_DATA 9 | float random(int min, int max) 10 | { 11 | int range = (int)(rand()) % (100 * (max - min)); 12 | return min + (float)range / 100; 13 | } 14 | 15 | int readMessage(int messageId, char *payload) 16 | { 17 | float temperature = random(20, 30); 18 | snprintf(payload, 19 | BUFFER_SIZE, 20 | "{ \"deviceId\": \"Raspberry Pi - C\", \"messageId\": %d, \"temperature\": %f, \"humidity\": %f }", 21 | messageId, 22 | temperature, 23 | random(60, 80)); 24 | return (temperature > TEMPERATURE_ALERT) ? 1 : 0; 25 | } 26 | 27 | #else 28 | int mask_check(int check, int mask) 29 | { 30 | return (check & mask) == mask; 31 | } 32 | 33 | // check whether the BMEInitMark's corresponding mark bit is set, if not, try to invoke corresponding init() 34 | int check_bme_init() 35 | { 36 | // wiringPiSetup == 0 is successful 37 | if (mask_check(BMEInitMark, WIRINGPI_SETUP) != 1 && wiringPiSetup() != 0) 38 | { 39 | return -1; 40 | } 41 | BMEInitMark |= WIRINGPI_SETUP; 42 | 43 | // wiringPiSetup < 0 means error 44 | if (mask_check(BMEInitMark, SPI_SETUP) != 1 && wiringPiSPISetup(SPI_CHANNEL, SPI_CLOCK) < 0) 45 | { 46 | return -1; 47 | } 48 | BMEInitMark |= SPI_SETUP; 49 | 50 | // bme280_init == 1 is successful 51 | if (mask_check(BMEInitMark, BME_INIT) != 1 && bme280_init(SPI_CHANNEL) != 1) 52 | { 53 | return -1; 54 | } 55 | BMEInitMark |= BME_INIT; 56 | return 1; 57 | } 58 | 59 | // check the BMEInitMark value is equal to the (WIRINGPI_SETUP | SPI_SETUP | BME_INIT) 60 | 61 | int readMessage(int messageId, char *payload) 62 | { 63 | if (check_bme_init() != 1) 64 | { 65 | // setup failed 66 | return -1; 67 | } 68 | 69 | float temperature, humidity, pressure; 70 | if (bme280_read_sensors(&temperature, &pressure, &humidity) != 1) 71 | { 72 | return -1; 73 | } 74 | 75 | snprintf(payload, 76 | BUFFER_SIZE, 77 | "{ \"deviceId\": \"Raspberry Pi - C\", \"messageId\": %d, \"temperature\": %f, \"humidity\": %f }", 78 | messageId, 79 | temperature, 80 | humidity); 81 | return temperature > TEMPERATURE_ALERT ? 1 : 0; 82 | } 83 | #endif 84 | 85 | void blinkLED() 86 | { 87 | digitalWrite(LED_PIN, HIGH); 88 | delay(100); 89 | digitalWrite(LED_PIN, LOW); 90 | } 91 | 92 | void setupWiring() 93 | { 94 | if (wiringPiSetup() == 0) 95 | { 96 | BMEInitMark |= WIRINGPI_SETUP; 97 | } 98 | pinMode(LED_PIN, OUTPUT); 99 | } 100 | -------------------------------------------------------------------------------- /wiring.h: -------------------------------------------------------------------------------- 1 | /* 2 | * IoT Hub Raspberry Pi C - Microsoft Sample Code - Copyright (c) 2017 - Licensed MIT 3 | */ 4 | 5 | #ifndef WIRING_H_ 6 | #define WIRING_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "./config.h" 14 | 15 | #define WIRINGPI_SETUP 1 16 | 17 | #if !SIMULATED_DATA 18 | #include "./bme280.h" 19 | 20 | #define SPI_CHANNEL 0 21 | #define SPI_CLOCK 1000000L 22 | 23 | #define SPI_SETUP 1 << 2 24 | #define BME_INIT 1 << 3 25 | #endif 26 | 27 | #define TEMPERATURE_ALERT 30 28 | 29 | int readMessage(int messageId, char *payload); 30 | void blinkLED(); 31 | void setupWiring(); 32 | 33 | #endif // WIRING_H_ 34 | --------------------------------------------------------------------------------