🌲 An indoor infrared receiver and transmitter with a temperature sensor 🎐
// #define DEBUG
#include "DebugUtils.h"
#include "src/serial/serial.h"
#include "src/sensor/sensor.h"
#include "src/webusb/webusb.h"
#include "src/infrared/infrared.h"
#include "src/led/led.h"
#include <FlashStorage.h>
#define MAX_IR_LEN 300
typedef struct {
int sizeofON;
uint16_t rawDataON[MAX_IR_LEN];
int sizeofOFF;
uint16_t rawDataOFF[MAX_IR_LEN];
int interval;
int duration;
int temperature;
bool valid;
} IRRawCode;
IRRawCode userConfig;
IRRawCode readConfig;
int config[3];
int configIndex = 0;
bool isRecordingON = false;
bool isRecordingOFF = false;
bool isConfigMode = false;
bool hasSentUserConfig = false;
FlashStorage(my_flash_store, IRRawCode);
void setup() {
#ifdef DEBUG
initSerial();
SerialUSB.println("Start Pine...");
#endif
initWebUSBSerial();
if (initSensor()) {
DEBUG_PRINT(readTemperature());
DEBUG_PRINT(readHumidity());
} else {
DEBUG_TITLE("Error: Si7021 Sensor not found.")
}
initIR();
initLED();
readConfig = my_flash_store.read();
}
void loop() {
// Scenario: Very first setup
if (!readConfig.valid && !isConfigMode) {
DEBUG_TITLE("Add user config at https://hutscape.com/pine/webusb");
blink();
}
// Scenario: Normal operation
if (readConfig.valid && !isConfigMode) {
DEBUG_TITLE("Do demo task: Turn on/off aircon every 5s");
doTask();
}
// Scenario: Config mode
if (isConfigMode) {
if (!hasSentUserConfig && readConfig.valid) {
DEBUG_TITLE("Sending user config to web browser");
sendUserConfig();
hasSentUserConfig = true;
}
if (receiveIR()) {
if (isValidIRCode()) {
receiveIRFromUser();
}
enableIR();
}
}
// All scenarios: alway check if web usb is connected
if (isWebUSBAvailable()) {
receiveConfigFromUser(readWebUSB());
}
}
// Print out raw IR code received from the user pressing a remote control
void receiveIRFromUser() {
int irSize = getIRcodeSize();
uint16_t irCode[MAX_IR_LEN];
getIRCode(irCode, MAX_IR_LEN);
DEBUG_TITLE("Received user IR code...");
#ifdef DEBUG
SerialUSB.print(F("#define RAW_DATA_LEN "));
SerialUSB.println(irSize, DEC);
SerialUSB.print(F("uint16_t rawData[RAW_DATA_LEN]={\n"));
for (int i = 1; i < irSize; i++) {
SerialUSB.print(irCode[i], DEC);
SerialUSB.print(F(", "));
}
SerialUSB.println(F("1000};\n"));
#endif
// TODO: Abstract out into a single function
if (isRecordingON) {
userConfig.sizeofON = irSize;
for (int i = 0; i < irSize - 1; i++) {
userConfig.rawDataON[i] = irCode[i+1];
}
userConfig.rawDataON[irSize - 1] = 1000;
isRecordingON = false;
} else if (isRecordingOFF) {
userConfig.sizeofOFF = irSize;
for (int i = 0; i < irSize - 1; i++) {
userConfig.rawDataOFF[i] = irCode[i+1];
}
userConfig.rawDataOFF[irSize - 1] = 1000;
isRecordingOFF = false;
}
writeWebUSB((const uint8_t *)irCode, irSize*2);
}
// Emit IR code that is stored in the flash memory
void doTask() {
delay(5000);
sendIR(readConfig.rawDataON, readConfig.sizeofON, 36);
DEBUG_TITLE("Sent Turn ON Aircon");
delay(5000);
sendIR(readConfig.rawDataOFF, readConfig.sizeofOFF, 36);
DEBUG_TITLE("Sent Turn OFF Aircon");
}
// Read config values from the user and store in flash memory
void receiveConfigFromUser(int byte) {
if (byte == 'A') {
DEBUG_TITLE("Recording ON IR command");
isRecordingON = true;
} else if (byte == 'B') {
DEBUG_TITLE("Recording OFF IR command");
isRecordingOFF = true;
} else if (byte == '1') {
DEBUG_TITLE("Connected via Web USB");
isConfigMode = true;
hasSentUserConfig = false;
} else {
config[configIndex++] = byte;
if (configIndex == 3) {
configIndex = 0;
DEBUG_TITLE("Received user config from the browser");
userConfig.interval = config[0];
DEBUG_PRINT(userConfig.interval);
userConfig.duration = config[1];
DEBUG_PRINT(userConfig.duration);
userConfig.temperature = config[2];
DEBUG_PRINT(userConfig.temperature);
printWebUSB("From MCU: Received all user configurations.");
userConfig.valid = true;
isConfigMode = false;
storeUserConfig();
}
}
}
void sendUserConfig() {
DEBUG_PRINT(readConfig.interval);
DEBUG_PRINT(readConfig.duration);
DEBUG_PRINT(readConfig.temperature);
// NOTE: Send to the web browser as a JSON string
String config = "{\"interval\":"
+ String(readConfig.interval)
+ ", \"duration\":"
+ String(readConfig.duration)
+ ", \"temperature\":"
+ String(readConfig.temperature)
+ "}";
printWebUSB(config);
}
void storeUserConfig() {
my_flash_store.write(userConfig);
readConfig = my_flash_store.read();
}
BOARD?=arduino:samd:arduino_zero_native
PORT := $(shell ls /dev/cu.usbmodem*)
.PHONY: default lint all flash clean
default: lint all flash clean
lint:
cpplint --extensions=ino --filter=-legal/copyright,-whitespace/line_length,-readability/casting,-readability/todo *.ino
all:
# This custom PCB does not have a crytal on pins PA00 and PA01
# Hence, use -DCRYSTALLESS to replace the extra_flags in boards.txt
arduino-cli compile --fqbn $(BOARD) --build-properties build.extra_flags="-DCRYSTALLESS -D__SAMD21G18A__ {build.usb_flags}" ./
flash:
arduino-cli upload -p $(PORT) --fqbn $(BOARD)
server:
echo "Open Chrome browser at http://localhost:8000"
python -m SimpleHTTPServer 8000
clean:
rm -r build