TheBlue Phoenix
Created July 14, 2020 © GPL3+

Fever Seeker

An affordable, light, thermal imaging device for EBT measurements that considers various factors that might affect its thermal accuracy.

ExpertFull instructions provided10 days133
Fever Seeker

Things used in this project

Hardware components

SEEK mosaic core 320x240 starter kit
×1
balenaFin
balenaFin
×1
Silicon Labs Thunder Board Sense V2
×1
Ultrasonic Sensor - HC-SR04 (Generic)
Ultrasonic Sensor - HC-SR04 (Generic)
×1

Software apps and online services

Silicon Labs Thunderboard
Solid Works
Cmake
Raspbian
Raspberry Pi Raspbian
MATLAB
MATLAB
ThingSpeak API
ThingSpeak API
Silicon Labs Simplicity Studio

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Drill / Driver, Cordless
Drill / Driver, Cordless

Story

Read more

Custom parts and enclosures

Device Design Files

It contains the Design files of the various 3D printable parts that make up the case of the device

Schematics

Schematic/Block Diagram

It shows basic connections (Wired and Wireless) between all main blocks of the device developed in this project

Code

ThingSpeak code

Python
Code to exchange data between Balena Pi and Thunderboard Sense V2 over Bluetooth
from __future__ import division
import sys
from bluepy.btle import *
import struct
import thread
from time import sleep
import urllib2

PRIVATE_KEY = ''

# Base URL of Thingspeak
baseURL = 'https://api.thingspeak.com/update?api_key='


def Read_SENSE():
    scanner = Scanner(0)
    devices = scanner.scan(2)
    for dev in devices:
        print "Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi)

        for (adtype, desc, value) in dev.getScanData():
            print "  %s = %s" % (desc, value)
    num_ble = len(devices)
    print num_ble
    if num_ble == 0:
        return None
    ble_service = []
    char_sensor = 0
    non_sensor = 0
    TVOC_char = Characteristic
    eCO2_char = Characteristic
    Pressure_char = Characteristic
    Sound_char = Characteristic
    temperature_char = Characteristic
    humidity_char = Characteristic

    #bat_char = Characteristic
    
    count = 15

    for i in range(num_ble):
        try:
            devices[i].getScanData()
            ble_service.append(Peripheral())
            ble_service[char_sensor].connect('00:0b:57:36:63:ff',devices[i].addrType)
            #ble_service[char_sensor].connect(devices[i].addr, devices[i].addrType)
            char_sensor = char_sensor + 1
            print "Connected %s device with addr %s " % (char_sensor, devices[i].addr)
        except:
            non_sensor = non_sensor + 1
    try:
        for i in range(char_sensor):

            services = ble_service[i].getServices()
            characteristics = ble_service[i].getCharacteristics()
            for k in characteristics:
                print k
                if k.uuid == "efd658ae-c401-ef33-76e7-91b00019103b":
                    print "eCO2 Level"
                    eCO2_char = k
                if k.uuid == "efd658ae-c402-ef33-76e7-91b00019103b":
                    print "TVOC Level"
                    TVOC_char = k
                if k.uuid == "00002a6d-0000-1000-8000-00805f9b34fb":
                    print "Pressure Level"
                    Pressure_char = k
                if k.uuid == "c8546913-bf02-45eb-8dde-9f8754f4a32e":
                    print "Sound Level"
                    Sound_char = k
                if k.uuid == "00002a6e-0000-1000-8000-00805f9b34fb":
                    print "Temperature"
                    temperature_char = k
                if k.uuid == "00002a6f-0000-1000-8000-00805f9b34fb":
                    print "Humidity"
                    humidity_char = k
                if k.uuid == "2a19":
                    print "Battery Level"
                    bat_char = k

    except:
        return None
    while True:
        # units of ppb
        TVOC_data = TVOC_char.read()
        TVOC_data_value = ord(TVOC_data/100)

        #units of ppm
        eCO2_data = eCO2_char.read()
        eCO2_data_value = ord(eCO2_data[0])

        # pressure is in units of 0.1Pa
        Pressure_data = Pressure_char.read()
        Pressure_data_value = (Pressure_data * 10)

        # units of 0.01dB
        Sound_data = Sound_char.read()
        Sound_data_value = (Sound_data * 100)

        bat_data = bat_char.read()
        bat_data_value = ord(bat_data[0])

        #convert from farenheit
        temperature_data = temperature_char.read()
        temperature_data_value = (ord(temperature_data[1]) << 8) + ord(temperature_data[0])
        float_temperature_data_value = (temperature_data_value / 100)

        humidity_data = humidity_char.read()
	humidity_data_value =(ord(humidity_data[1])<<8)+ord(humidity_data[0])

	print "TVOC: ", TVOC_data_value
	print eCO2: ", eCO2_data_value
	print Pressure: ", Pressure_data_value
	print Sound: ", Sound_data_value
	print Temperature: , float_temperature_data_value
	print Humidity: , humidity_data_value

	if count > 14:

        	f = urllib.urlopen(baseURL + PRIVATE_KEY + "&field1=%s&field2=%s&field3=%s&field4=%s&field5=%s&field6=%s" % (TVOC_data_value, eCO2_data_value, Pressure_data_value, Sound_data_value, float_temperature_data_value, humidity_data_value))
        	print f.read()
        	f.close()
        	count = 0
        	count = count + 1
        	sleep(1)

while True:
    Read_SENSE()

Edited source code for the seekware-simple example that generates a CSV file of thermal values

C/C++
Use it in place of the original seekware-simple.c file to generate more precise thermal values with frame stamps. Alternate frames are recorded.
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <signal.h>
#include <math.h>
#include <time.h>

#ifdef _WIN32
#include <windows.h>
#include <winnt.h>
#define inline __inline
#endif

#if defined(__linux__) || defined(__APPLE__)
#include <sys/time.h>
#endif

#include <seekware/seekware.h>

#define NUM_CAMS            	5
#define LOG_THERMOGRAPHY_DATA	true

bool exit_requested = false;

static inline double wall_clock_s(void) {
#ifdef _WIN32
	LARGE_INTEGER time;
	LARGE_INTEGER freq;
	QueryPerformanceFrequency(&freq);
	QueryPerformanceCounter(&time);
	return (double)time.QuadPart / freq.QuadPart;
#else
	struct timeval time;
	gettimeofday(&time, NULL);
	return (double)time.tv_sec + (double)time.tv_usec * .000001;
#endif
}

static inline void print_fw_info(psw camera)
{
    sw_retcode status; 
    int therm_ver;

    printf("Model Number:%s\n",camera->modelNumber);
    printf("SerialNumber: %s\n", camera->serialNumber);
    printf("Manufacture Date: %s\n", camera->manufactureDate);

    printf("Firmware Version: %u.%u.%u.%u\n",
            camera->fw_version_major,
			camera->fw_version_minor,
            camera->fw_build_major,
			camera->fw_build_minor);

	status = Seekware_GetSettingEx(camera, SETTING_THERMOGRAPHY_VERSION, &therm_ver, sizeof(therm_ver));
	if (status != SW_RETCODE_NONE) {
		fprintf(stderr, "Error: Seek GetSetting returned %i\n", status);
	}
    printf("Themography Version: %i\n", therm_ver);

	sw_sdk_info sdk_info;
	Seekware_GetSdkInfo(NULL, &sdk_info);
	printf("Image Processing Version: %u.%u.%u.%u\n",
		sdk_info.lib_version_major,
		sdk_info.lib_version_minor,
		sdk_info.lib_build_major,
		sdk_info.lib_build_minor);

	printf("\n");
    fflush(stdout);
}

static void signal_callback(int signum)
{
    printf("Exit requested!\n");
    exit_requested = true;
}

int main(int argc, char * argv [])
{
	double start = 0.0f;
	double stop = 0.0f;
	double frametime = 0.0f;
	double framerate = 0.0f;

	float spot = 0;
	float min = 0;
	float max = 0;
	float timestamp_s = 0.0f;

	uint32_t field_count = 0;
	uint32_t enable = 1;
	uint64_t timestamp_us = 0;
	uint64_t frame_count = 0;
	//uint16_t a = 0;

	size_t camera_pixels = 0;

	sw_retcode status;
	psw camera = NULL;
	psw camera_list[NUM_CAMS];

	float* thermography_data = NULL;
	uint16_t* filtered_data = NULL;

	signal(SIGINT, signal_callback);
	signal(SIGTERM, signal_callback);

	printf("seekware-simple - A simple data capture utility for Seek Thermal cameras\n\n");

	sw_sdk_info sdk_info;
	Seekware_GetSdkInfo(NULL, &sdk_info);
	printf("SDK Version: %u.%u\n\n", sdk_info.sdk_version_major, sdk_info.sdk_version_minor);

	int num_cameras_found = 0;
	status = Seekware_Find(camera_list, NUM_CAMS, &num_cameras_found);
	if (status != SW_RETCODE_NONE || num_cameras_found == 0) {
		printf("Cannot find any cameras...exiting\n");
		return 1;
	}

	camera = camera_list[0];
	status = Seekware_Open(camera);
	if (status != SW_RETCODE_NONE) {
		fprintf(stderr, "Cannot open camera : %d\n", status);
		goto cleanup;
	}

	// Must read firmware info AFTER the camera has been opened
	printf("::Camera Firmware Info::\n");
	print_fw_info(camera);

	camera_pixels = (size_t)camera->frame_cols * (size_t)camera->frame_rows;

	//Allocate buffers for holding thermal data
	thermography_data = (float*)malloc(camera_pixels * sizeof(float));
	if (thermography_data == NULL) {
		fprintf(stderr, "Cannot allocate thermography buffer!\n");
		goto cleanup;
	}

	filtered_data = (uint16_t*)malloc((camera_pixels + camera->frame_cols) * sizeof(uint16_t));
	if (filtered_data == NULL) {
		fprintf(stderr, "Cannot allocate filtered buffer!\n");
		goto cleanup;
	}

	Seekware_SetSettingEx(camera, SETTING_ENABLE_TIMESTAMP, &enable, sizeof(enable));
	Seekware_SetSettingEx(camera, SETTING_RESET_TIMESTAMP, &enable, sizeof(enable));

	start = wall_clock_s();

	/* * * * * * * * * * * * * Data Capture Loop * * * * * * * * * * * * * * */

	do {
		status = Seekware_GetImageEx(camera, filtered_data, thermography_data, NULL);
		if (status == SW_RETCODE_NOFRAME) {
			printf("Seek Camera Timeout ...\n");
		}
		if (status == SW_RETCODE_DISCONNECTED) {
			printf("Seek Camera Disconnected ...\n");
		}
		if (status != SW_RETCODE_NONE) {
			printf("Seek Camera Error : %u ...\n", status);
			break;
		}

		status = Seekware_GetSpot(camera, &spot, &min, &max);
		if (status != SW_RETCODE_NONE) {
			break;
		}

		++frame_count;

		// Calculate the frame rate
		stop = wall_clock_s();
		frametime = stop - start;
		framerate = (1 / frametime);
		start = wall_clock_s();

		//Writes every 10th thermography frame to a csv file.
#if LOG_THERMOGRAPHY_DATA
		
		if (frame_count % 2 == 0) {
			FILE* log = fopen("thermography.csv", "a");
			for (uint16_t i = 0; i < camera->frame_rows; ++i) {
				for (uint16_t j = 0; j < camera->frame_cols; ++j) {
					float value = thermography_data[(i * camera->frame_cols) + j];
					//float rounded_value = roundf(10.0f * value) / 10.0f;
					fprintf(log, "%.4f,", value);
				}
				fputc('\n', log);
			}
			fprintf(log, "%d\n", frame_count);
			fclose(log);
		}
#endif
		 // Extract telemetry data
		 field_count = *(uint32_t*)&filtered_data[camera_pixels];
		 timestamp_us = *(uint64_t*)& filtered_data[camera_pixels + 5];
		 timestamp_s = (float)timestamp_us / 1.0e6f;

	    //Update metrics on stdout
        //On select cameras that do not support thermography, nan is returned for spot, min, and max
		if (!exit_requested) {
			if (frame_count > 1) {
				static const int num_lines = 17;
				for (int i = 0; i < num_lines; i++) {
					printf("\033[A");
				}
			}
			printf("\r\33[2K Frame Info:\n");
			printf("\33[2K--------------------------\n");
			printf("\33[2K frame_width:  %u\n",		 camera->frame_cols);
			printf("\33[2K field_height: %u\n",		 camera->frame_rows);
			printf("\33[2K frame_count:  %llu\n",	 frame_count);
			printf("\33[2K field_count:  %u\n",		 field_count);
			printf("\33[2K frame_rate:   %.1ffps\n", framerate);
			printf("\33[2K timestamp:    %.6fs\n",	 timestamp_s);
			printf("\33[2K--------------------------\n");

			printf("\n\33[2K Temperature Info:\n");
			printf("\33[2K--------------------------\n");
			printf("\33[2K\33\33[2K\x1B[41m\x1B[37m max: %*.1fC \x1B[0m\n", 3, max);
			printf("\33[2K\33\33[2K\x1B[42m\x1B[37m spot:%*.1fC \x1B[0m\n", 3, spot);
			printf("\33[2K\33\33[2K\x1B[44m\x1B[37m min: %*.1fC \x1B[0m\n", 3, min);
			printf("\33[2K--------------------------\n\n");

			fflush(stdout);
		}
	} while (!exit_requested);

/* * * * * * * * * * * * * Cleanup * * * * * * * * * * * * * * */
cleanup:
	
	printf("Exiting...\n");

	if (camera != NULL) {
		Seekware_Close(camera);
	}

	if (thermography_data != NULL) {
		free(thermography_data);
	}

	if (filtered_data != NULL){
		free(filtered_data);
	}

	return 0;
}

Seek Starter kit SDK

Makefile
It is the SDK for Linux (and Windows) for the thermal imaging camera used.
No preview (download only).

MATLAB script to use the CSV example code

MATLAB
Use it in MATLAB to break CSV file of thermal values into chunks of individual frames
clear;
%T = csvread('2degC_13cm.csv');
%T = csvread('face_dishant.csv');
T = csvread('thermography.csv');
rows = size(T,1);
columns = size(T,2);
frame = ones(rows/241,240,320);
A = ones(240,320);

for i=0:1:rows/241-1
    frame(i+1,:,:) = T(241*i+1:241*i+240,1:320);
end

Credits

TheBlue Phoenix

TheBlue Phoenix

7 projects • 32 followers
I have represented India Internationally. I have knowledge of various boards and embedded coding, FPGAs, TI, ADI, On Semi, RPi, SBCs etc.

Comments