# Project export: ARiot

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2024
- Tagline: Combining augmented reality and IoT devices to connect the digital and physical worlds.
- Devpost: https://devpost.com/software/ar-iot
- GitHub: https://github.com/jmaff/treehacks-2024/
- Video: https://www.youtube.com/embed/oEBZAhjI6jY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — jmaff (12 commits), mschubs (7 commits), MustafaMiyaziwala (1 commits)

## Devpost submission (written by the team)

### Inspiration

As computer engineering majors, we came into Treehacks knowing we wanted to work with hardware. Perhaps the most notable recent hardware releases are those of mixed reality headsets focused on passthrough, such as the Apple Vision Pro and Meta Quest 3. As we thought about these exciting trends in immersive AR, we realized a critical gap: a way to bridge your AR experience with the physical world around you. Naturally, this led us to the world of IoT, since so much of our world is already connected to the internet and ready to be integrated into mixed reality.

### What it does

This is a fun project that puts an adaptive visual display and controls on top of IoT devices in an augmented reality environment. These displays are interactive and informative. For example, our app puts a virtual button on top of a “smart” light source (which we achieve with an LED). A user can toggle this button to turn the light on and off from a panel rendered directly anchored to the real device in AR. We also have a temperature sensor and encoder which are enhanced through displaying the current decimal reading above them. We think there are many potential applications of such technology, especially if it were adopted for devices such as the Apple Vision Pro, which people may end up wearing for extended periods of time. For example, this tech could provide a fun and convenient experience for interacting with various devices around your house, allowing you to quickly check the temperature in your kitchen or control a smart lamp with a glance. Multiple people wearing an AR headset could even walk into a room and view the same customizable information, and be allowed to interact with real devices in easy or novel ways.

### How we built it

The IoT devices are two Particle Photons, each connected to a humidity/temperature sensor, a rotary encoder, and an LED. The Photons publish their sensor data every three seconds. Clients can also request action from their devices (or Things), such as turning on a light. We wrote the backend for this project in the form of AWS Lambda functions using Python, and hosted persistent storage via a Mongo Atlas database. We built the app in Swift using ARKit and OpenCV. To identify the IoT devices, we used ArUco fiducial markers, whose locations we could determine quite consistently and accurately using OpenCV.

### Challenges we ran into

We had initially planned to look for device markers through the Oculus Quest 2 using OpenXR SDK. After going through several hurdles getting the SDK setup, we found that the Quest 2 restricts access to the passthrough stream, prohibiting marker detection. This led us to pivot to using ARkit. We also had to combine the different tech stacks each of us were working on, which required careful planning and strong communication.

### Accomplishments we're proud of

Overall, we’re proud of the scope of our project and the fact that we were able to integrate such a variety of technologies. We also all made sure to step outside of our comfort zones. Each team member chose the tech stack we were least familiar with and learned a ton.

### What we learned

We learned a wide range of technologies from firmware to server-side software to frontend tech. As a team, we gained experience using ARKit, building API endpoints on AWS, and writing firmware in C++.

### What's next

for AR-iot We were able to produce a cool MVP after this hackathon, but there is still a good amount of optimization and some improvements to think about. With more time, we could move away from Particle towards lower latency options, and we could customize the UI to include more devices and richer interaction. Some next steps would also be to use this tech with AR headsets that are commercially available, particularly the Apple Vision Pro, assuming that options are provided to developers to incorporate actual camera data for object tracking while still respecting user privacy.

## README (from the GitHub repository)

Welcome to ARiot!

In this project, we visually enhance and upgrade interactivity of sensors and iot devices with augmented reality.

lambdasFunctions/
contains aws lambdas functions that act as API endpoints connecting our edge devices, mongo database, and iOS app.

ios/
contains the code that renders interactive displays over devices through ARKit.
It was originally inspired by https://github.com/dparksports/aruco-arkit-opencv.


## Detected evidence (automated analysis)

Indexed codebase: 18 recognized source files, 47 KB.
- C (language) — detected in the code
- C++ (language) — detected in the code
- Python (language) — detected in the code
- Swift (language) — detected in the code
- AWS (technology) — claimed on Devpost, not found in the code
- MongoDB (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (32 of 32)

```
ios/.DS_Store
ios/.gitignore
ios/DetailScene.swift
ios/Immersed IoT.xcodeproj/project.pbxproj
ios/Immersed IoT.xcodeproj/project.xcworkspace/contents.xcworkspacedata
ios/Immersed IoT.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
ios/Immersed IoT/AppDelegate.swift
ios/Immersed IoT/Assets.xcassets/AccentColor.colorset/Contents.json
ios/Immersed IoT/Assets.xcassets/AppIcon.appiconset/Contents.json
ios/Immersed IoT/Assets.xcassets/Contents.json
ios/Immersed IoT/Base.lproj/LaunchScreen.storyboard
ios/Immersed IoT/Base.lproj/Main.storyboard
ios/Immersed IoT/ClickableView.swift
ios/Immersed IoT/cv/SKWorldTransform.h
ios/Immersed IoT/cv/SKWorldTransform.mm
ios/Immersed IoT/cv/TagDetector.h
ios/Immersed IoT/cv/TagDetector.mm
ios/Immersed IoT/Immersed_IoT-Bridging-Header.h
ios/Immersed IoT/TagNode.swift
ios/Immersed IoT/ThingView.swift
ios/Immersed IoT/ViewController.swift
ios/Thing.swift
lambdaFunctions/.gitignore
lambdaFunctions/getDeviceState.py
lambdaFunctions/toggleDevice.py
lambdaFunctions/updateDeviceState.py
particle/ariot.ino
particle/DHT.cpp
particle/DHT.h
particle/RotaryEncoder.cpp
particle/RotaryEncoder.h
README.md
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Merge branch 'main' of https://github.com/jmaff/treehacks-2024
- Final
- Add particle
- Create README.md
- removed pycache
- small changes
- everything works lambda function wise
- Working demo
- removed some info
- [node] Remove nodes after not being seen for 5 seconds
- [organize] Fix gitignore location for xcode
- Merge branch 'main' of https://github.com/jmaff/treehacks-2024
- [node] Add button, styling
- fixed gitignore location
- added some lambda function code
- [node] Naive (broken) node view implementation
- [tracking] Parity with example
- [organize] Move app into separate dir, implement TagNode
- ARKit + OpenCV skeleton
- Initial Commit

## Key source files (fetched from GitHub, selected and truncated for size)

### ios/Thing.swift

```swift
//
//  Thing.swift
//  Immersed IoT
//
//  Created by Joseph Maffetone on 2/18/24.
//

import Foundation

struct Thing : Codable {
    var tagID: Int
    var particleID: String
    var type: String
    var mostRecentState: Double
    
    static func getButtonName(tagID: Int) -> String{
        return String(format: "button%d", tagID)
    }
}

```

### lambdaFunctions/getDeviceState.py

```python
import json
from pymongo import MongoClient
from secret_info import mongo_username, mongo_password

# Get MongoDB Atlas connection details from environment variables

def lambda_handler(event, context):
    body = json.loads(event['body'])

    tagID = body['tagID']
    # Connect to MongoDB Atlas
    cluster = MongoClient(f"mongodb+srv://{mongo_username}:{mongo_password}@cluster0.mckkmip.mongodb.net/?retryWrites=true&w=majority")
    db = cluster["treehacks_2024"]
    collection = db["iot_data"]
    
    # Query MongoDB Atlas with the provided ID
    result = collection.find_one({'tagID':tagID})
    if result is not None:
        del result["_id"]

    # Close the MongoDB Atlas connection
    cluster.close()

    return {
        'statusCode': 200,
        'body': json.dumps(result)  # Return the query result
    }

# event = {'id': '0'}
# print(lambda_handler(event, 0))

```

### ios/DetailScene.swift

```swift
//
//  DetailScene.swift
//  Immersed IoT
//
//  Created by Joseph Maffetone on 2/17/24.
//

import SpriteKit

class DetailScene: SKScene {

    override func didMove(to view: SKView) {
        // Create a clickable button
        let button = SKLabelNode(text: "Click me!")
        button.position = CGPoint(x: size.width / 2, y: size.height / 2)
        button.fontSize = 20
        button.fontColor = SKColor.white
        button.name = "clickableButton"
        addChild(button)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        for touch in touches {
            let location = touch.location(in: self)
            let touchedNode = atPoint(location)

            // Check if the touched node is the clickable button
            if touchedNode.name == "clickableButton" {
                // Handle button click here
                print("Button clicked!")
            }
        }
    }
}

```

### lambdaFunctions/updateDeviceState.py

```python
import json
import requests
from pymongo import MongoClient
from secret_info import mongo_username, mongo_password, particle_token

# Get MongoDB Atlas connection details from environment variables

def lambda_handler(event, context):
    body = json.loads(event['body'])

    # Connect to MongoDB Atlas
    cluster = MongoClient(f"mongodb+srv://{mongo_username}:{mongo_password}@cluster0.mckkmip.mongodb.net/?retryWrites=true&w=majority")
    db = cluster["treehacks_2024"]
    collection = db["iot_data"]
    
    filter = {"particleID": body['coreid']}
    new_val = float(body['data'])
    update = {"$set": {"mostRecentState": new_val}}
    # Query MongoDB Atlas with the provided ID

    result = collection.find_one_and_update(filter, update, return_document=True)
    if result is not None:
        del result["_id"]
 
    # Close the MongoDB Atlas connection
    cluster.close()

    return {
        'statusCode': 200,
        'body': json.dumps(result)  # Return the query result
    }

```

### lambdaFunctions/toggleDevice.py

```python
import requests
import json
from pymongo import MongoClient
from secret_info import mongo_username, mongo_password, particle_token

# Get MongoDB Atlas connection details from environment variables

def lambda_handler(event, context):
    body = json.loads(event['body'])
    tagID = body['tagID']
    # Connect to MongoDB Atlas
    cluster = MongoClient(f"mongodb+srv://{mongo_username}:{mongo_password}@cluster0.mckkmip.mongodb.net/?retryWrites=true&w=majority")
    db = cluster["treehacks_2024"]
    collection = db["iot_data"]
    functionName = body['functionName']
    
    # Query MongoDB Atlas with the provided ID
    result = collection.find_one({'tagID':tagID})
    device_id = result['particleID']

    # Close the MongoDB Atlas connection
    cluster.close()

    # API endpoint URL
    url = f'https://api.particle.io/v1/devices/{device_id}/{functionName}'

    # Set up headers with authorization token
    payload = {'arg':'', 'access_token': particle_token}

    # Make a POST request to an API endpoint
    response = requests.post(url, data=payload)

    return json.dumps("")

# if __name__ == "__main__":
#     event = {'id': '0', 'functionName': 'LED'}
#     lambda_handler(event, 0)
```

### particle/RotaryEncoder.h

```c
// -----
// RotaryEncoder.h - Library for using rotary encoders.
// This class is implemented for use with the Arduino environment.
//
// Copyright (c) by Matthias Hertel, http://www.mathertel.de
//
// This work is licensed under a BSD 3-Clause style license,
// https://www.mathertel.de/License.aspx.
//
// More information on: http://www.mathertel.de/Arduino
// -----
// 18.01.2014 created by Matthias Hertel
// 16.06.2019 pin initialization using INPUT_PULLUP
// 10.11.2020 Added the ability to obtain the encoder RPM
// 29.01.2021 Options for using rotary encoders with 2 state changes per latch.
// -----

#ifndef RotaryEncoder_h
#define RotaryEncoder_h

#include "Arduino.h"

class RotaryEncoder
{
public:
  enum class Direction {
    NOROTATION = 0,
    CLOCKWISE = 1,
    COUNTERCLOCKWISE = -1
  };

  enum class LatchMode {
    FOUR3 = 1, // 4 steps, Latch at position 3 only (compatible to older versions)
    FOUR0 = 2, // 4 steps, Latch at position 0 (reverse wirings)
    TWO03 = 3  // 2 steps, Latch at position 0 and 3 
  };

  // ----- Constructor -----
  RotaryEncoder(int pin1, int pin2, LatchMode mode = LatchMode::FOUR0);

  // retrieve the current position
  long getPosition();

  // simple retrieve of the direction the knob was rotated last time. 0 = No rotation, 1 = Clockwise, -1 = Counter Clockwise
  Direction getDirection();

  // adjust the current position
  void setPosition(long newPosition);

  // call this function every some milliseconds or by using an interrupt for handling state changes of the rotary encoder.
  void tick(void);

  // Returns the time in milliseconds between the current observed
  unsigned long getMillisBetweenRotations() const;

  // Returns the RPM
  unsigned long getRPM();

private:
  int _pin1, _pin2; // Arduino pins used for the encoder.
  
  LatchMode _mode; // Latch mode from initialization

  volatile int8_t _oldState;

  volatile long _position;        // Internal position (4 times _positionExt)
  volatile long _positionExt;     // External position
  volatile long _positionExtPrev; // External position (used only for direction checking)

  unsigned long _positionExtTime;     // The time the last position change was detected.
  unsigned long _positionExtTimePrev; // The time the previous position change was detected.
};

#endif

// End
```

### particle/DHT.h

```c
/*!
 *  @file DHT.h
 *
 *  This is a library for DHT series of low cost temperature/humidity sensors.
 *
 *  You must have Adafruit Unified Sensor Library library installed to use this
 * class.
 *
 *  Adafruit invests time and resources providing this open source code,
 *  please support Adafruit andopen-source hardware by purchasing products
 *  from Adafruit!
 *
 *  Written by Adafruit Industries.
 *
 *  MIT license, all text above must be included in any redistribution
 */

#ifndef DHT_H
#define DHT_H

#include "Arduino.h"

/* Uncomment to enable printing out nice debug messages. */
//#define DHT_DEBUG

#define DEBUG_PRINTER                                                          \
  Serial /**< Define where debug output will be printed.                       \
          */

/* Setup debug printing macros. */
#ifdef DHT_DEBUG
#define DEBUG_PRINT(...)                                                       \
  { DEBUG_PRINTER.print(__VA_ARGS__); }
#define DEBUG_PRINTLN(...)                                                     \
  { DEBUG_PRINTER.println(__VA_ARGS__); }
#else
#define DEBUG_PRINT(...)                                                       \
  {} /**< Debug Print Placeholder if Debug is disabled */
#define DEBUG_PRINTLN(...)                                                     \
  {} /**< Debug Print Line Placeholder if Debug is disabled */
#endif

/* Define types of sensors. */
static const uint8_t DHT11{11};  /**< DHT TYPE 11 */
static const uint8_t DHT12{12};  /**< DHY TYPE 12 */
static const uint8_t DHT21{21};  /**< DHT TYPE 21 */
static const uint8_t DHT22{22};  /**< DHT TYPE 22 */
static const uint8_t AM2301{21}; /**< AM2301 */

#if defined(TARGET_NAME) && (TARGET_NAME == ARDUINO_NANO33BLE)
#ifndef microsecondsToClockCycles
/*!
 * As of 7 Sep 2020 the Arduino Nano 33 BLE boards do not have
 * microsecondsToClockCycles defined.
 */
#define microsecondsToClockCycles(a) ((a) * (SystemCoreClock / 1000000L))
#endif
#endif

/*!
 *  @brief  Class that stores state and functions for DHT
 */
class DHT {
public:
  DHT(uint8_t pin, uint8_t type, uint8_t count = 6);
  void begin(uint8_t usec = 55);
  float readTemperature(bool S = false, bool force = false);
  float convertCtoF(float);
  float convertFtoC(float);
  float computeHeatIndex(bool isFahrenheit = true);
  float computeHeatIndex(float temperature, float percentHumidity,
                         bool isFahrenheit = true);
  float readHumidity(bool force = false);
  bool read(bool force = false);

private:
  uint8_t data[5];
  uint8_t _pin, _type;
#ifdef __AVR
  // Use direct GPIO access on an 8-bit AVR so keep track of the port and
  // bitmask for the digital pin connected to the DHT.  Other platforms will use
  // digitalRead.
  uint8_t _bit, _port;
#endif
  uint32_t _lastreadtime, _maxcycles;
  bool _lastresult;
  uint8_t pullTime; // Time (in usec) to pull up data line before reading

  uint32_t expectPulse(bool level);
};

/*!
 *  @brief  Class that defines Interrupt Lock Avaiability
 */
class InterruptLock {
public:
  InterruptLock() {
#if !defined(ARDUINO_ARCH_NRF52)
    noInterrupts();
#endif
  }
  ~InterruptLock() {
#if !defined(ARDUINO_ARCH_NRF52)
    interrupts();
#endif
  }
};

#endif
```

### particle/RotaryEncoder.cpp

```c++
// -----
// RotaryEncoder.cpp - Library for using rotary encoders.
// This class is implemented for use with the Arduino environment.
//
// Copyright (c) by Matthias Hertel, http://www.mathertel.de
//
// This work is licensed under a BSD 3-Clause style license,
// https://www.mathertel.de/License.aspx.
//
// More information on: http://www.mathertel.de/Arduino
// -----
// Changelog: see RotaryEncoder.h
// -----

#include "RotaryEncoder.h"
#include "Arduino.h"

#define LATCH0 0 // input state at position 0
#define LATCH3 3 // input state at position 3


// The array holds the values �1 for the entries where a position was decremented,
// a 1 for the entries where the position was incremented
// and 0 in all the other (no change or not valid) cases.

const int8_t KNOBDIR[] = {
    0, -1, 1, 0,
    1, 0, 0, -1,
    -1, 0, 0, 1,
    0, 1, -1, 0};


// positions: [3] 1 0 2 [3] 1 0 2 [3]
// [3] is the positions where my rotary switch detends
// ==> right, count up
// <== left,  count down


// ----- Initialization and Default Values -----

RotaryEncoder::RotaryEncoder(int pin1, int pin2, LatchMode mode)
{
  // Remember Hardware Setup
  _pin1 = pin1;
  _pin2 = pin2;
  _mode = mode;

  // Setup the input pins and turn on pullup resistor
  pinMode(pin1, INPUT_PULLUP);
  pinMode(pin2, INPUT_PULLUP);

  // when not started in motion, the current state of the encoder should be 3
  int sig1 = digitalRead(_pin1);
  int sig2 = digitalRead(_pin2);
  _oldState = sig1 | (sig2 << 1);

  // start with position 0;
  _position = 0;
  _positionExt = 0;
  _positionExtPrev = 0;
} // RotaryEncoder()


long RotaryEncoder::getPosition()
{
  return _positionExt;
} // getPosition()


RotaryEncoder::Direction RotaryEncoder::getDirection()
{
  RotaryEncoder::Direction ret = Direction::NOROTATION;

  if (_positionExtPrev > _positionExt) {
    ret = Direction::COUNTERCLOCKWISE;
    _positionExtPrev = _positionExt;
  } else if (_positionExtPrev < _positionExt) {
    ret = Direction::CLOCKWISE;
    _positionExtPrev = _positionExt;
  } else {
    ret = Direction::NOROTATION;
    _positionExtPrev = _positionExt;
  }

  return ret;
}


void RotaryEncoder::setPosition(long newPosition)
{
  switch (_mode) {
  case LatchMode::FOUR3:
  case LatchMode::FOUR0:
    // only adjust the external part of the position.
    _position = ((newPosition << 2) | (_position & 0x03L));
    _positionExt = newPosition;
    _positionExtPrev = newPosition;
    break;

  case LatchMode::TWO03:
    // only adjust the external part of the position.
    _position = ((newPosition << 1) | (_position & 0x01L));
    _positionExt = newPosition;
    _positionExtPrev = newPosition;
    break;
  } // switch

} // setPosition()


void RotaryEncoder::tick(void)
{
  int sig1 = digitalRead(_pin1);
  int sig2 = digitalRead(_pin2);
  int8_t thisState = sig1 | (sig2 << 1);

  if (_oldState != thisState) {
    _position += KNOBDIR[thisState | (_oldState << 2)];
    _oldState = thisState;

    switch (_mode) {
    case LatchMode::FOUR3:
      if (thisState == LATCH3) {
        // The hardware has 4 steps with a latch on the input state 3
        _positionExt = _position >> 2;
        _positionExtTimePrev = _positionExtTime;
        _positionExtTime = millis();
      }
      break;

    case LatchMode::FOUR0:
      if (thisState == LATCH0) {
        // The hardware has 4 steps with a latch on the input state 0
        _positionExt = _position >> 2;
        _positionExtTimePrev = _positionExtTime;
        _positionExtTime = millis();
      }
      break;

    case LatchMode::TWO03:
      if ((thisState == LATCH0) || (thisState == LATCH3)) {
        // The hardware has 2 steps with a latch on the input state 0 and 3
        _positionExt = _position >> 1;
        _positionExtTimePrev = _positionExtTime;
        _positionExtTime = millis();
      }
      break;
    } // switch
  } // if
} // tick()


unsigned long RotaryEncoder::getMillisBetweenRotations() const
{
  return (_positionExtTime - _positionExtTimePrev);
}

unsigned long RotaryEncoder::getRPM()
{
  // calculate max of difference in time between last position changes or last change and now.
  unsigned long timeBetweenLastPositions = _positionExtTime - _positionExtTimePrev;
  unsigned long timeToLastPosition = millis() - _positionExtTime;
  unsigned long t = max(timeBetweenLastPositions, timeToLastPosition);
  return 60000.0 / ((float)(t * 20));
}


// End
```

### particle/DHT.cpp

```c++
/*!
 *  @file DHT.cpp
 *
 *  @mainpage DHT series of low cost temperature/humidity sensors.
 *
 *  @section intro_sec Introduction
 *
 *  This is a library for DHT series of low cost temperature/humidity sensors.
 *
 *  You must have Adafruit Unified Sensor Library library installed to use this
 * class.
 *
 *  Adafruit invests time and resources providing this open source code,
 *  please support Adafruit andopen-source hardware by purchasing products
 *  from Adafruit!
 *
 *  @section author Author
 *
 *  Written by Adafruit Industries.
 *
 *  @section license License
 *
 *  MIT license, all text above must be included in any redistribution
 */

#include "DHT.h"

#define MIN_INTERVAL 2000 /**< min interval value */
#define TIMEOUT                                                                \
  UINT32_MAX /**< Used programmatically for timeout.                           \
                   Not a timeout duration. Type: uint32_t. */

/*!
 *  @brief  Instantiates a new DHT class
 *  @param  pin
 *          pin number that sensor is connected
 *  @param  type
 *          type of sensor
 *  @param  count
 *          number of sensors
 */
DHT::DHT(uint8_t pin, uint8_t type, uint8_t count) {
  (void)count; // Workaround to avoid compiler warning.
  _pin = pin;
  _type = type;
#ifdef __AVR
  _bit = digitalPinToBitMask(pin);
  _port = digitalPinToPort(pin);
#endif
  _maxcycles =
      microsecondsToClockCycles(1000); // 1 millisecond timeout for
                                       // reading pulses from DHT sensor.
  // Note that count is now ignored as the DHT reading algorithm adjusts itself
  // based on the speed of the processor.
}

/*!
 *  @brief  Setup sensor pins and set pull timings
 *  @param  usec
 *          Optionally pass pull-up time (in microseconds) before DHT reading
 *starts. Default is 55 (see function declaration in DHT.h).
 */
void DHT::begin(uint8_t usec) {
  // set up the pins!
  pinMode(_pin, INPUT_PULLUP);
  // Using this value makes sure that millis() - lastreadtime will be
  // >= MIN_INTERVAL right away. Note that this assignment wraps around,
  // but so will the subtraction.
  _lastreadtime = millis() - MIN_INTERVAL;
  DEBUG_PRINT("DHT max clock cycles: ");
  DEBUG_PRINTLN(_maxcycles, DEC);
  pullTime = usec;
}

/*!
 *  @brief  Read temperature
 *  @param  S
 *          Scale. Boolean value:
 *					- true = Fahrenheit
 *					- false = Celcius
 *  @param  force
 *          true if in force mode
 *	@return Temperature value in selected scale
 */
float DHT::readTemperature(bool S, bool force) {
  float f = NAN;

  if (read(force)) {
    switch (_type) {
    case DHT11:
      f = data[2];
      if (data[3] & 0x80) {
        f = -1 - f;
      }
      f += (data[3] & 0x0f) * 0.1;
      if (S) {
        f = convertCtoF(f);
      }
      break;
    case DHT12:
      f = data[2];
      f += (data[3] & 0x0f) * 0.1;
      if (data[2] & 0x80) {
        f *= -1;
      }
      if (S) {
        f = convertCtoF(f);
      }
      break;
    case DHT22:
    case DHT21:
      f = ((word)(data[2] & 0x7F)) << 8 | data[3];
      f *= 0.1;
      if (data[2] & 0x80) {
        f *= -1;
      }
      if (S) {
        f = convertCtoF(f);
      }
      break;
    }
  }
  return f;
}

/*!
 *  @brief  Converts Celcius to Fahrenheit
 *  @param  c
 *					value in Celcius
 *	@return float value in Fahrenheit
 */
float DHT::convertCtoF(float c) { return c * 1.8 + 32; }

/*!
 *  @brief  Converts Fahrenheit to Celcius
 *  @param  f
 *					value in Fahrenheit
 *	@return float value in Celcius
 */
float DHT::convertFtoC(float f) { return (f - 32) * 0.55555; }

/*!
 *  @brief  Read Humidity
 *  @param  force
 *					force read mode
 *	@return float value - humidity in percent
 */
float DHT::readHumidity(bool force) {
  float f = NAN;
  if (read(force)) {
    switch (_type) {
    case DHT11:
    case DHT12:
      f = data[0] + data[1] * 0.1;
      break;
    case DHT22:
    case DHT21:
      f = ((word)data[0]) << 8 | data[1];
      f *= 0.1;
      break;
    }
  }
  return f;
}

/*!
 *  @brief  Compute Heat Index
 *          Simplified version that reads temp and humidity from sensor
 *  @param  isFahrenheit
 * 					true if fahrenheit, false if celcius
 *(default true)
 *	@return float heat index
 */
float DHT::computeHeatIndex(bool isFahrenheit) {
  float hi = computeHeatIndex(readTemperature(isFahrenheit), readHumidity(),
                              isFahrenheit);
  return hi;
}

/*!
 *  @brief  Compute Heat Index
 *  				Using both Rothfusz and Steadman's equations
 *					(http://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml)
 *  @param  temperature
 *          temperature in selected scale
 *  @param  percentHumidity
 *          humidity in percent
 *  @param  isFahrenheit
 * 					true if fahrenheit, false if celcius
 *	@return float heat index
 */
float DHT::computeHeatIndex(float temperature, float percentHumidity,
                            bool isFahrenheit) {
  float hi;

  if (!isFahrenheit)
    temperature = convertCtoF(temperature);

  hi = 0.5 * (temperature + 61.0 + ((temperature - 68.0) * 1.2) +
              (percentHumidity * 0.094));

  if (hi > 79) {
    hi = -42.379 + 2.04901523 * temperature + 10.14333127 * percentHumidity +
         -0.22475541 * temperature * percentHumidity +
         -0.00683783 * pow(temperature, 2) +
         -0.05481717 * pow(percentHumidity, 2) +
         0.00122874 * pow(temperature, 2) * percentHumidity +
         0.00085282 * temperature * pow(percentHumidity, 2) +
         -0.00000199 * pow(temperature, 2) * pow(percentHumidity, 2);

    if ((percentHumidity < 13) && (temperature >= 80.0) &&
        (temperature <= 112.0))
      hi -= ((13.0 - percentHumidity) * 0.25) *
            sqrt((17.0 - abs(temperature - 95.0)) * 0.05882)
[truncated — 6541 more characters]
```

### ios/Immersed IoT/Immersed_IoT-Bridging-Header.h

```c
//
//  Immersed_IoT-Bridging-Header.h
//  Immersed IoT
//
//  Created by Joseph Maffetone on 2/17/24.
//

//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "TagDetector.h"
#import "SKWorldTransform.h"


```

[7 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]