Skip to content

Instantly share code, notes, and snippets.

@geegaz
Created June 30, 2023 08:52
Show Gist options
  • Save geegaz/e62f3d8cf7bd9b650231b882cd3fc2a9 to your computer and use it in GitHub Desktop.
Save geegaz/e62f3d8cf7bd9b650231b882cd3fc2a9 to your computer and use it in GitHub Desktop.
OVUM - Arduino code
#define SECRET_SSID "OVUMConnection"
#define SECRET_PASS "OVUMConnection"
/*
Based on the Simple UDP example by Tom Igoe
*/
#include <SPI.h>
#include <WiFiNINA.h>
#include "arduino_secrets.h"
#include "components.h"
#include "protocol.h"
#define BUFFER_SIZE 256
#define TICK_RATE 10
unsigned int localPort = 8889;
IPAddress broadcastIp(255, 255, 255, 255);
WiFiUDP udp;
byte buffer[256];
Data data;
EyeLightActor eyl;
HeartbeatActor hbt;
PettingSensor pts;
//UprightSensor upr;
unsigned long lastMillis = 0;
int packetDelay = 0;
void setup() {
Serial.begin(9600);
setupComponents();
data.reset();
// Connection
delay(3000);
}
void loop() {
int delta = millis() - lastMillis;
lastMillis = millis();
processConnection(delta);
}
void processConnection(int delta) {
switch (WiFi.status()) {
case WL_NO_SHIELD:
Serial.println("No available WiFi shield or module");
delay(2000);
break;
case WL_CONNECTED:
// Update components
// (only when the connection is active)
processComponents(delta);
/* Should not be needed if it only listens
if (packetDelay < (1000 / TICK_RATE)) {
packetDelay += delta;
break;
}
packetDelay = 0;
*/
// Read/write packets
while (udp.parsePacket()) {
// Read all packets available
int bytes = udp.read(buffer, BUFFER_SIZE - 1);
buffer[bytes] = 0;
data.parseIn(buffer, bytes);
IPAddress remoteIp = udp.remoteIP();
unsigned int remotePort = udp.remotePort();
if (udp.beginPacket(remoteIp, remotePort)) {
// Send a new packet
int bytes = data.parseOut(buffer, BUFFER_SIZE);
buffer[bytes] = 0;
udp.write(buffer, bytes);
udp.endPacket();
}
}
break;
default:
data.reset();
resetComponents();
// Disconnected - try to connect to the given network
Serial.print("Attempting to connect to network: ");
Serial.println(SECRET_SSID); // print the network name (SSID)
WiFi.begin(SECRET_SSID, SECRET_PASS); // try to connect
//delay(2000);
if (WiFi.status() == WL_CONNECTED) {
Serial.print("Connected at IP address ");
Serial.println(WiFi.localIP());
udp.begin(localPort);
}
break;
}
}
void setupComponents() {
// Actors
eyl.setup();
hbt.setup();
// Sensors
pts.setup();
//upr.setup();
}
void processComponents(int delta) {
// Actors
eyl.color[0] = data.eye_color_r;
eyl.color[1] = data.eye_color_g;
eyl.color[2] = data.eye_color_b;
eyl.updateBrightness(data.eye_brightness);
eyl.process(delta);
hbt.beatSpeed = data.stress_level;
hbt.beatIntensity = data.stamina_level;
hbt.process(delta);
// Sensors
pts.process(delta);
data.petting = pts.value;
//upr.process(delta);
//data.upright = upr.value;
}
void resetComponents() {
// Actors
eyl.reset();
hbt.reset();
// Sensors don't need to be reset
// - this only happens when you lose the connection
}
#include <Adafruit_NeoPixel.h>
#define PIEZO_FILTER_SAMPLES 8
struct MinMax {
int minValue;
int maxValue;
MinMax() {}
MinMax(int _min, int _max)
: minValue(_min), maxValue(_max) {}
int interpolate(float time) {
return minValue + time * (maxValue - minValue);
}
int remap(int _min, int _max, int value) {
float t = float(value - _min) / float(_max - _min);
return interpolate(t);
}
};
struct HeartbeatActor {
// ACTOR
// Unity -> Arduino
const int MOTOR_PIN_0 = 2; // vibrator Grove connected to digital pin 2
const int MOTOR_PIN_1 = 3; // vibrator Grove connected to digital pin 3
const float LONG_BEAT_RATIO = 0.75;
const float SHORT_BEAT_RATIO = 0.25;
const float LONG_BEAT_MAX_TIME = 80;
const float SHORT_BEAT_MAX_TIME = 40;
MinMax cycleTimeRange = MinMax(400, 1600);
int cycleTime = 1000;
float beatSpeed = 0.0;
float beatIntensity = 1.0;
bool stopped = false;
int pulseStep = 0;
unsigned long stepDelay = 0;
int stepDelays[4];
void setup() {
pinMode(MOTOR_PIN_0, OUTPUT);
pinMode(MOTOR_PIN_1, OUTPUT);
updateDelays();
}
void process(int delta) {
if (stepDelay <= 0) {
switch (pulseStep) {
case 0: // First pulse ON
digitalWrite(MOTOR_PIN_0, HIGH);
digitalWrite(MOTOR_PIN_1, HIGH);
break;
case 1: // First pulse OFF
digitalWrite(MOTOR_PIN_0, LOW);
digitalWrite(MOTOR_PIN_1, LOW);
break;
case 2: // Second pulse ON
digitalWrite(MOTOR_PIN_0, HIGH);
digitalWrite(MOTOR_PIN_1, HIGH);
break;
case 3: // Second pulse OFF
digitalWrite(MOTOR_PIN_0, LOW);
digitalWrite(MOTOR_PIN_1, LOW);
break;
}
updateDelays();
stepDelay = stepDelays[pulseStep];
pulseStep = (pulseStep + 1) % 4;
} else {
stepDelay -= min(stepDelay, delta); // Avoid overflow
}
// DEBUG
//Serial.println(delta);
//Serial.println(pulseStep);
//Serial.println(delta);
}
void reset() {
digitalWrite(MOTOR_PIN_0, LOW);
digitalWrite(MOTOR_PIN_1, LOW);
pulseStep = 0;
stepDelay = 0;
beatSpeed = 0.0;
beatIntensity = 1.0;
stopped = false;
}
// 0 0.5 1
// |-----:-----|-----:-----|
// [long][short]
// First beat ON -> 0.8 * 0.25
// First beat OFF -> cycleTime * 0.25 - <First beat ON>
// Second beat ON -> cycleTime * (0.25 * SHORT_BEAT_TIME * beatIntensity)
// Second beat OFF -> cycleTime * 0.75 - <Second beat ON>
void updateDelays() {
cycleTime = cycleTimeRange.interpolate(1.0 - beatSpeed);
stepDelays[0] = min(cycleTime * (0.25 * LONG_BEAT_RATIO), LONG_BEAT_MAX_TIME) * beatIntensity;
stepDelays[1] = cycleTime * 0.25 - stepDelays[0];
stepDelays[2] = min(cycleTime * (0.25 * SHORT_BEAT_RATIO), SHORT_BEAT_MAX_TIME) * beatIntensity;
stepDelays[3] = cycleTime * 0.75 - stepDelays[2];
// DEBUG
//Serial.println(stepDelays[0]);
//Serial.println(stepDelays[1]);
//Serial.println(stepDelays[2]);
//Serial.println(stepDelays[3]);
}
};
struct EyeLightActor {
// ACTOR
// Unity -> Arduino
const int STRIP_PIN_1 = 5;
const int STRIP_PIN_2 = 6;
const int STRIP_LENGTH = 1;
Adafruit_NeoPixel strip1 = Adafruit_NeoPixel(STRIP_LENGTH, STRIP_PIN_1, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip2 = Adafruit_NeoPixel(STRIP_LENGTH, STRIP_PIN_2, NEO_GRB + NEO_KHZ800);
byte color[3] = {0, 0, 0};
void setup() {
strip1.begin();
strip2.begin();
}
void process(int delta) {
for (size_t i = 0; i < STRIP_LENGTH; i++) {
// Eye 1
strip1.setPixelColor(i, color[0], color[1], color[2]);
strip1.show();
// Eye 2
strip2.setPixelColor(i, color[0], color[1], color[2]);
strip2.show();
}
}
void reset() {
for (size_t i = 0; i < STRIP_LENGTH; i++) {
color[0] = 0;
color[1] = 0;
color[2] = 0;
updateBrightness(0.0);
}
}
void updateBrightness(float value = 1.0) {
color[0] = color[0] * value;
color[1] = color[1] * value;
color[2] = color[2] * value;
}
};
struct PettingSensor {
// SENSOR
// Arduino -> Unity
const int LED_PIN = 4; // led Grove connected to digital pin 4
const int PIEZO_PIN = A0; // Piezo output
const int RISE_TIME = 200; // ms to reach max calming
const int LOWER_TIME = 800; // ms to reach min calming
const float THRESHOLD = 0.5f; // min pettingAmount for value to be true
float pettingAmount = 0.0f; // 0 -> 1
bool value = false;
// Don't need this anymore
// int filterValue = 0;
// size_t filterIndex = 0;
// int filter[PIEZO_FILTER_SAMPLES];
void setup() {
// DEBUG
pinMode(LED_PIN, OUTPUT);
}
void process(int delta) {
int piezoAnalog = analogRead(PIEZO_PIN);
float piezoValue = piezoAnalog / 1023.0 * 5.0f;
// Don't need this anymore
// filter[filterIndex] = piezoAnalog;
// filterValue = 0;
// for(size_t f = 0; f < PIEZO_FILTER_SAMPLES; f++) {
// filterValue += filter[f];
// }
// filterValue /= PIEZO_FILTER_SAMPLES;
// filterIndex = (filterIndex + 1) % PIEZO_FILTER_SAMPLES;
float deltaValue = delta;
if (piezoValue > 0.1f)
pettingAmount += deltaValue / RISE_TIME;
else
pettingAmount -= deltaValue / LOWER_TIME;
pettingAmount = constrain(pettingAmount, 0.0f, 1.0f);
value = (pettingAmount > THRESHOLD);
// DEBUG
digitalWrite(LED_PIN, value); // Light up the debug led
//Serial.println(filterValue); // Print the filtered value.
}
};
struct UprightSensor {
// SENSOR
// Arduino -> Unity
const int BUTTON_PIN = 7;
bool value = false;
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
}
void process(int delta) {
bool last_value = value;
value = digitalRead(BUTTON_PIN);
// DEBUG
//if (last_value != value) Serial.println(value);
}
};
/* OVUM Protocol
Received from Unity:
'c' -> Eye color, RGBA color sent as 4 bytes
's' -> Stress, value between 0 and 1 as 1 byte
'l' -> Stamina, value between 0 and 1 as 1 byte
'r' -> Reset event, resets the data
Sent from Arduino:
'f' -> Petting the egg, sent as 1 byte
'u' -> Holding the egg upright, sent as 1 byte
(bools are exchanged as 127 = true and 255 = false since 0 indicates the end of a packet)
*/
struct Data {
// Eye color
byte eye_color_r = 0;
byte eye_color_g = 0;
byte eye_color_b = 0;
float eye_brightness = 0.0;
// Heartbeat
float stress_level = 0.0;
float stamina_level = 1.0;
// Petting
bool petting;
bool upright;
void reset() {
eye_color_r = 0;
eye_color_g = 0;
eye_color_b = 0;
eye_brightness = 0;
stress_level = 0.0;
stamina_level = 1.0;
petting = false;
upright = false;
}
/// Writes values in a data buffer
///
///
size_t parseOut(byte* data, size_t length) {
size_t packet_size = 0;
packet_size += 2; // Petting identifier + Petting value -> 2 bytes
packet_size += 2; // Upright identifier + Upright value -> 2 bytes
if (packet_size <= length) {
// Petting
data[0] = 'f';
data[1] = boolToByte(petting);
// Upright
data[2] = 'u';
data[3] = boolToByte(upright);
return packet_size;
}
return 0;
}
/// Reads values from a data buffer
///
///
size_t parseIn(byte* data, size_t length) {
size_t i = 0;
while (i < length) {
char identifier = data[i];
i++; // Identifier -> 1 byte
switch (identifier) {
case 'c':
if ((length - i) < 4) break; // Skip if not enough bytes left to read
eye_color_r = data[i];
eye_color_g = data[i+1];
eye_color_b = data[i+2];
eye_brightness = floatFromByte(data[i+3]);
i += 4; // RGBA -> 5 bytes
break;
case 's':
if ((length - i) < 1) break; // Skip if not enough bytes left to read
stress_level = floatFromByte(data[i]);
i += 1; // Speed -> 1 bytes
break;
case 'l':
if ((length - i) < 1) break; // Skip if not enough bytes left to read
stamina_level = floatFromByte(data[i]);
i += 1; // Speed -> 1 bytes
break;
case 'r':
reset();
break;
}
}
return i;
}
inline byte boolToByte(bool b) {
return b ? 255 : 127;
}
inline float floatFromByte(byte b) {
return (float)b / 255.0f;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment