├── temperature_sensor.png ├── README.md ├── application.fam ├── LICENSE └── temperature_sensor.c /temperature_sensor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xMasterX/AM2320_Flipper_Plugin/HEAD/temperature_sensor.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AM2320+ Flipper Zero Plugin 2 | 3 | ### How to connect 4 | 5 | ![Connection](https://user-images.githubusercontent.com/10697207/199586577-5c9cf516-2096-4d70-9e2f-1f9458a68d65.jpg) 6 | -------------------------------------------------------------------------------- /application.fam: -------------------------------------------------------------------------------- 1 | App( 2 | appid="am2320_temp_sensor", 3 | name="[AM2320] Temp. Sensor", 4 | apptype=FlipperAppType.EXTERNAL, 5 | entry_point="temperature_sensor_app", 6 | cdefines=["APP_TEMPERATURE_SENSOR"], 7 | requires=[ 8 | "gui", 9 | ], 10 | stack_size=2 * 1024, 11 | order=90, 12 | fap_icon="temperature_sensor.png", 13 | fap_category="GPIO", 14 | ) 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 MX 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 | -------------------------------------------------------------------------------- /temperature_sensor.c: -------------------------------------------------------------------------------- 1 | /* Flipper Plugin to read the values from a AM2320/AM2321 Sensor */ 2 | /* Created by @xMasterX, original app (was used as template) by Mywk - https://github.com/Mywk */ 3 | /* Lib used as reference: https://github.com/Gozem/am2320/blob/master/am2321.c*/ 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | 16 | #define TS_DEFAULT_VALUE 0xFFFF 17 | 18 | #define AM2320_ADDRESS (0x5C << 1) 19 | 20 | #define DATA_BUFFER_SIZE 8 21 | 22 | // External I2C BUS 23 | #define I2C_BUS &furi_hal_i2c_handle_external 24 | 25 | typedef enum { 26 | TSSInitializing, 27 | TSSNoSensor, 28 | TSSPendingUpdate, 29 | } TSStatus; 30 | 31 | typedef enum { 32 | TSEventTypeTick, 33 | TSEventTypeInput, 34 | } TSEventType; 35 | 36 | typedef struct { 37 | TSEventType type; 38 | InputEvent input; 39 | } TSEvent; 40 | 41 | extern const NotificationSequence sequence_blink_red_100; 42 | extern const NotificationSequence sequence_blink_blue_100; 43 | 44 | static TSStatus temperature_sensor_current_status = TSSInitializing; 45 | 46 | // Temperature and Humidity data buffers, ready to print 47 | char ts_data_buffer_temperature_c[DATA_BUFFER_SIZE]; 48 | char ts_data_buffer_temperature_f[DATA_BUFFER_SIZE]; 49 | char ts_data_buffer_relative_humidity[DATA_BUFFER_SIZE]; 50 | char ts_data_buffer_absolute_humidity[DATA_BUFFER_SIZE]; 51 | 52 | // CRC16 calculation 53 | static uint16_t get_crc16(const uint8_t* buf, size_t len) { 54 | uint16_t crc = 0xFFFF; 55 | 56 | while(len--) { 57 | crc ^= (uint16_t)*buf++; 58 | for(unsigned i = 0; i < 8; i++) { 59 | if(crc & 0x0001) { 60 | crc >>= 1; 61 | crc ^= 0xA001; 62 | } else { 63 | crc >>= 1; 64 | } 65 | } 66 | } 67 | 68 | return crc; 69 | } 70 | // Combine bytes 71 | static uint16_t combine_bytes(uint8_t msb, uint8_t lsb) { 72 | return ((uint16_t)msb << 8) | (uint16_t)lsb; 73 | } 74 | 75 | // Executes an I2C wake up, sends command and reads result 76 | // true if fetch was successful, false otherwise 77 | static bool temperature_sensor_get_data(uint8_t* buffer, uint8_t size) { 78 | uint32_t timeout = furi_ms_to_ticks(100); 79 | uint8_t cmdbuffer[3] = {0, 0, 0}; 80 | bool ret = false; 81 | 82 | // Aquire I2C bus 83 | furi_hal_i2c_acquire(I2C_BUS); 84 | 85 | // Wake UP AM2320 (sensor goes to sleep to not warm up and affect the humidity sensor) 86 | furi_hal_i2c_is_device_ready(I2C_BUS, (uint8_t)AM2320_ADDRESS, timeout); 87 | // Check if device woken up then we do next stuff 88 | if(furi_hal_i2c_is_device_ready(I2C_BUS, (uint8_t)AM2320_ADDRESS, timeout)) { 89 | // Wait a bit 90 | furi_delay_us(1000); 91 | 92 | // Prepare command: Addr 0x03, start register = 0x00, number of registers to read = 0x04 93 | cmdbuffer[0] = 0x03; 94 | cmdbuffer[1] = 0x00; 95 | cmdbuffer[2] = 0x04; 96 | 97 | // Transmit command to read registers 98 | ret = furi_hal_i2c_tx(I2C_BUS, (uint8_t)AM2320_ADDRESS, cmdbuffer, 3, timeout); 99 | 100 | // Wait a bit 101 | furi_delay_us(1600); 102 | if(ret) { 103 | /* 104 | * Read out 8 bytes of data 105 | * Byte 0: Should be Modbus function code 0x03 106 | * Byte 1: Should be number of registers to read (0x04) 107 | * Byte 2: Humidity msb 108 | * Byte 3: Humidity lsb 109 | * Byte 4: Temperature msb 110 | * Byte 5: Temperature lsb 111 | * Byte 6: CRC lsb byte 112 | * Byte 7: CRC msb byte 113 | */ 114 | ret = furi_hal_i2c_rx(I2C_BUS, (uint8_t)AM2320_ADDRESS, buffer, size, timeout); 115 | } 116 | } 117 | // Release i2c bus 118 | furi_hal_i2c_release(I2C_BUS); 119 | 120 | return ret; 121 | } 122 | 123 | // Fetches temperature and humidity from sensor 124 | // Temperature and humidity must be preallocated 125 | // true if fetch was successful, false otherwise 126 | static bool temperature_sensor_fetch_info(double* temperature, double* humidity) { 127 | *humidity = (float)0; 128 | bool ret = false; 129 | 130 | uint8_t buffer[8] = {0, 0, 0, 0, 0, 0, 0, 0}; 131 | 132 | // Fetch data from sensor 133 | ret = temperature_sensor_get_data(buffer, 8); 134 | 135 | // If we got no result 136 | if(!ret) return false; 137 | 138 | if(buffer[0] != 0x03) return false; // must be 0x03 modbus reply 139 | if(buffer[1] != 0x04) return false; // must be 0x04 number of registers reply 140 | 141 | // Check CRC16 sum, if not correct - return false 142 | uint16_t crcdata = get_crc16(buffer, 6); 143 | uint16_t crcread = combine_bytes(buffer[7], buffer[6]); 144 | if(crcdata != crcread) return false; 145 | 146 | // Combine bytes for temp and humidity 147 | uint16_t temp16 = combine_bytes(buffer[4], buffer[5]); 148 | uint16_t humi16 = combine_bytes(buffer[2], buffer[3]); 149 | 150 | /* Temperature resolution is 16Bit, 151 | * temperature highest bit (Bit15) is equal to 1 indicates a 152 | * negative temperature, the temperature highest bit (Bit15) 153 | * is equal to 0 indicates a positive temperature; 154 | * temperature in addition to the most significant bit (Bit14 ~ Bit0) 155 | * indicates the temperature sensor string value. 156 | * Temperature sensor value is a string of 10 times the 157 | * actual temperature value. 158 | */ 159 | if(temp16 & 0x8000) { 160 | temp16 = -(temp16 & 0x7FFF); 161 | } 162 | 163 | // Prepare output data 164 | *temperature = (float)temp16 / 10.0; 165 | *humidity = (float)humi16 / 10.0; 166 | 167 | return true; 168 | } 169 | 170 | // Draw callback 171 | 172 | static void temperature_sensor_draw_callback(Canvas* canvas, void* ctx) { 173 | UNUSED(ctx); 174 | 175 | canvas_clear(canvas); 176 | canvas_set_font(canvas, FontPrimary); 177 | canvas_draw_str(canvas, 2, 10, "AM2320/AM2321 Sensor"); 178 | 179 | canvas_set_font(canvas, FontSecondary); 180 | canvas_draw_str(canvas, 2, 62, "Press back to exit."); 181 | 182 | switch(temperature_sensor_current_status) { 183 | case TSSInitializing: 184 | canvas_draw_str(canvas, 2, 30, "Initializing.."); 185 | break; 186 | case TSSNoSensor: 187 | canvas_draw_str(canvas, 2, 30, "No sensor found!"); 188 | break; 189 | case TSSPendingUpdate: { 190 | canvas_draw_str(canvas, 3, 24, "Temperature"); 191 | canvas_draw_str(canvas, 68, 24, "Humidity"); 192 | 193 | // Draw vertical lines 194 | canvas_draw_line(canvas, 61, 16, 61, 50); 195 | canvas_draw_line(canvas, 62, 16, 62, 50); 196 | 197 | // Draw horizontal line 198 | canvas_draw_line(canvas, 2, 27, 122, 27); 199 | 200 | // Draw temperature and humidity values 201 | canvas_draw_str(canvas, 8, 38, ts_data_buffer_temperature_c); 202 | canvas_draw_str(canvas, 42, 38, "C"); 203 | canvas_draw_str(canvas, 8, 48, ts_data_buffer_temperature_f); 204 | canvas_draw_str(canvas, 42, 48, "F"); 205 | canvas_draw_str(canvas, 68, 38, ts_data_buffer_relative_humidity); 206 | canvas_draw_str(canvas, 100, 38, "%"); 207 | canvas_draw_str(canvas, 68, 48, ts_data_buffer_absolute_humidity); 208 | canvas_draw_str(canvas, 100, 48, "g/m3"); 209 | 210 | } break; 211 | default: 212 | break; 213 | } 214 | } 215 | 216 | // Input callback 217 | 218 | static void temperature_sensor_input_callback(InputEvent* input_event, void* ctx) { 219 | furi_assert(ctx); 220 | FuriMessageQueue* event_queue = ctx; 221 | 222 | TSEvent event = {.type = TSEventTypeInput, .input = *input_event}; 223 | furi_message_queue_put(event_queue, &event, FuriWaitForever); 224 | } 225 | 226 | // Timer callback 227 | 228 | static void temperature_sensor_timer_callback(FuriMessageQueue* event_queue) { 229 | furi_assert(event_queue); 230 | 231 | TSEvent event = {.type = TSEventTypeTick}; 232 | furi_message_queue_put(event_queue, &event, 0); 233 | } 234 | 235 | // App entry point 236 | 237 | int32_t temperature_sensor_app(void* p) { 238 | UNUSED(p); 239 | 240 | furi_hal_power_suppress_charge_enter(); 241 | // Declare our variables and assign variables a default value 242 | TSEvent tsEvent; 243 | bool sensorFound = false; 244 | double celsius, fahrenheit, rel_humidity, abs_humidity = TS_DEFAULT_VALUE; 245 | 246 | // Used for absolute humidity calculation 247 | double vapour_pressure = 0; 248 | 249 | FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(TSEvent)); 250 | 251 | // Register callbacks 252 | ViewPort* view_port = view_port_alloc(); 253 | view_port_draw_callback_set(view_port, temperature_sensor_draw_callback, NULL); 254 | view_port_input_callback_set(view_port, temperature_sensor_input_callback, event_queue); 255 | 256 | // Create timer and register its callback 257 | FuriTimer* timer = 258 | furi_timer_alloc(temperature_sensor_timer_callback, FuriTimerTypePeriodic, event_queue); 259 | furi_timer_start(timer, furi_kernel_get_tick_frequency()); 260 | 261 | // Register viewport 262 | Gui* gui = furi_record_open(RECORD_GUI); 263 | gui_add_view_port(gui, view_port, GuiLayerFullscreen); 264 | 265 | // Used to notify the user by blinking red (error) or blue (fetch successful) 266 | NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION); 267 | 268 | while(1) { 269 | furi_check(furi_message_queue_get(event_queue, &tsEvent, FuriWaitForever) == FuriStatusOk); 270 | 271 | // Handle events 272 | if(tsEvent.type == TSEventTypeInput) { 273 | // Exit on back key 274 | if(tsEvent.input.key == 275 | InputKeyBack) // We dont check for type here, we can check the type of keypress like: (event.input.type == InputTypeShort) 276 | break; 277 | 278 | } else if(tsEvent.type == TSEventTypeTick) { 279 | // Update sensor data 280 | // Fetch data and set the sensor current status accordingly 281 | sensorFound = temperature_sensor_fetch_info(&celsius, &rel_humidity); 282 | temperature_sensor_current_status = (sensorFound ? TSSPendingUpdate : TSSNoSensor); 283 | 284 | if(sensorFound) { 285 | // Blink blue 286 | notification_message(notifications, &sequence_blink_blue_100); 287 | 288 | if(celsius != TS_DEFAULT_VALUE && rel_humidity != TS_DEFAULT_VALUE) { 289 | // Convert celsius to fahrenheit 290 | fahrenheit = (celsius * 9 / 5) + 32; 291 | 292 | // Calculate absolute humidity - For more info refer to https://github.com/Mywk/FlipperTemperatureSensor/issues/1 293 | // Calculate saturation vapour pressure first 294 | vapour_pressure = 295 | (double)6.11 * 296 | pow(10, (double)(((double)7.5 * celsius) / ((double)237.3 + celsius))); 297 | // Then the vapour pressure in Pa 298 | vapour_pressure = vapour_pressure * rel_humidity; 299 | // Calculate absolute humidity 300 | abs_humidity = 301 | (double)2.16679 * (double)(vapour_pressure / ((double)273.15 + celsius)); 302 | 303 | // Fill our buffers here, not on the canvas draw callback 304 | snprintf(ts_data_buffer_temperature_c, DATA_BUFFER_SIZE, "%.2f", celsius); 305 | snprintf(ts_data_buffer_temperature_f, DATA_BUFFER_SIZE, "%.2f", fahrenheit); 306 | snprintf( 307 | ts_data_buffer_relative_humidity, DATA_BUFFER_SIZE, "%.2f", rel_humidity); 308 | snprintf( 309 | ts_data_buffer_absolute_humidity, DATA_BUFFER_SIZE, "%.2f", abs_humidity); 310 | } 311 | 312 | } else { 313 | // Reset our variables to their default values 314 | celsius = fahrenheit = rel_humidity = abs_humidity = TS_DEFAULT_VALUE; 315 | 316 | // Blink red 317 | notification_message(notifications, &sequence_blink_red_100); 318 | } 319 | } 320 | 321 | uint32_t wait_ticks = furi_ms_to_ticks(!sensorFound ? 100 : 500); 322 | furi_delay_tick(wait_ticks); 323 | } 324 | 325 | furi_hal_power_suppress_charge_exit(); 326 | // Dobby is freee (free our variables, Flipper will crash if we don't do this!) 327 | furi_timer_free(timer); 328 | gui_remove_view_port(gui, view_port); 329 | view_port_free(view_port); 330 | furi_message_queue_free(event_queue); 331 | 332 | furi_record_close(RECORD_NOTIFICATION); 333 | furi_record_close(RECORD_GUI); 334 | 335 | return 0; 336 | } 337 | --------------------------------------------------------------------------------