Skip to content

Instantly share code, notes, and snippets.

@connornishijima
Created December 20, 2023 02:28
Show Gist options
  • Save connornishijima/33c9c7a16fa37c2f84eff7598828dce2 to your computer and use it in GitHub Desktop.
Save connornishijima/33c9c7a16fa37c2f84eff7598828dce2 to your computer and use it in GitHub Desktop.
SB Light Transfer Receiver
/*
This is designed to run on stock Sensory Bridge hardware, and uses the Sweet Spot LEDs to receive data
from the tool at https://sensorybridge.rocks/light_transfer/ when you hold your phone upside down 2-3
inches above the LEDs! Binary data is sent through screen flashes and decoded by this Arduino Sketch,
printing the result to the Serial Monitor. It uses a CRC8 byte at the end of every packet to determine
the validity of the data sent, but no forward error correction like Hamming/Reed-Solomon codes yet.
Unfortunately, I can't guarantee this works on anything but a stock Sensory Bridge with OEM LEDs, so
this will never be the only option available for sending data to the device, but one of many.
- Connor
*/
#include <FastLED.h>
#define DATA_PIN 36
#define NUM_LEDS 128
CRGB leds[NUM_LEDS];
#define SWEET_SPOT_LEFT_PIN 7
#define SWEET_SPOT_CENTER_PIN 8
#define SWEET_SPOT_RIGHT_PIN 9
#define SWEET_SPOT_CENTER_CHANNEL 2
enum packet_states {
WAITING,
PACKET_LENGTH_RX,
PACKET_DATA_RX,
PACKET_CRC8_RX,
NUM_PACKET_STATES
};
uint8_t current_packet_state = WAITING;
bool current_state = false;
uint32_t last_switch = 0;
const uint16_t value_history_length = 128;
uint16_t value_history[value_history_length];
uint8_t bit_history[24];
uint8_t bit_history_ascii[3];
uint16_t bit_rx_count = 0;
uint8_t packet_length = 0;
uint8_t chars_rx = 0;
uint8_t packet_contents[256];
uint8_t packet_bit_index = 0;
uint8_t packet_byte_index = 0;
uint8_t calculated_CRC8 = 0;
uint8_t packet_CRC8 = 0;
uint32_t wait_start = 0;
struct CRGBF {
float r;
float g;
float b;
};
CRGBF standby_color = { 8, 8, 64 };
CRGBF current_color = standby_color;
float current_position = 0.5;
float current_width = 10.0;
float current_amplitude = 1.0;
CRGBF target_color = standby_color;
float target_position = 0.5;
float target_width = 10.0;
float target_amplitude = 0.0;
float breath_output = 0.5;
uint32_t t_now = 0;
// Function to generate CRC8 using polynomial 0x07
uint8_t calc_crc8(const uint8_t *data, size_t length) {
const uint8_t polynomial = 0x07;
uint8_t crc = 0xFF;
for (size_t i = 0; i < length; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80) {
crc = (crc << 1) ^ polynomial;
} else {
crc <<= 1;
}
}
}
return crc ^ 0xFF;
}
bool round_binary(float pulse_duration, float low_nominal, float high_nominal) {
float threshold = (low_nominal + high_nominal) / 2.0;
if (pulse_duration <= threshold) {
return 0;
} else {
return 1;
}
}
void interpolate_led_values() {
float step_value = 0.10;
static float sine_rotation = 0.0;
sine_rotation += 0.3;
float sine_output = sin(sine_rotation) * (0.05 * current_amplitude) + 0.5;
target_position = sine_output;
target_amplitude = 0.0;
static float breath_rotation = 0.0;
breath_rotation += 0.01;
breath_output = sin(breath_rotation) * 0.4 + 0.6;
if (current_color.r > target_color.r) {
float r_delta = current_color.r - target_color.r;
current_color.r -= (r_delta * step_value);
}
if (current_color.r < target_color.r) {
float r_delta = target_color.r - current_color.r;
current_color.r += (r_delta * step_value);
}
if (current_color.g > target_color.g) {
float g_delta = current_color.g - target_color.g;
current_color.g -= (g_delta * step_value);
}
if (current_color.g < target_color.g) {
float g_delta = target_color.g - current_color.g;
current_color.g += (g_delta * step_value);
}
if (current_color.b > target_color.b) {
float b_delta = current_color.b - target_color.b;
current_color.b -= (b_delta * step_value);
}
if (current_color.b < target_color.b) {
float b_delta = target_color.b - current_color.b;
current_color.b += (b_delta * step_value);
}
if (current_position > target_position) {
float position_delta = current_position - target_position;
current_position -= (position_delta * step_value);
}
if (current_position < target_position) {
float position_delta = target_position - current_position;
current_position += (position_delta * step_value);
}
if (current_width > target_width) {
float width_delta = current_width - target_width;
current_width -= (width_delta * step_value);
}
if (current_width < target_width) {
float width_delta = target_width - current_width;
current_width += (width_delta * step_value);
}
if (current_amplitude > target_amplitude) {
float amplitude_delta = current_amplitude - target_amplitude;
current_amplitude -= (amplitude_delta * step_value * 0.1);
}
if (current_amplitude < target_amplitude) {
float amplitude_delta = target_amplitude - current_amplitude;
current_amplitude += (amplitude_delta * step_value * 0.1);
}
if (current_amplitude < 0.01) {
current_amplitude = 0.00;
}
}
void setup() {
Serial.begin(230400);
FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS); // GRB ordering is assumed
target_color = standby_color;
target_width = 10;
//pinMode(SWEET_SPOT_CENTER_PIN, OUTPUT);
//ledcSetup(SWEET_SPOT_CENTER_CHANNEL, 500, 12);
//ledcAttachPin(SWEET_SPOT_CENTER_PIN, SWEET_SPOT_CENTER_CHANNEL);
//ledcWrite(SWEET_SPOT_CENTER_CHANNEL, 32);
//analogSetAttenuation(ADC_2_5db);
}
void loop() {
static uint8_t iter = 0;
iter++;
read_data_from_LED();
interpolate_led_values();
draw_LED_fade(current_color, current_position, current_width);
}
void read_data_from_LED() {
t_now = micros();
static uint32_t last_cross = t_now;
static bool current_state = LOW;
uint32_t value = 0;
// Shift array left
memmove(value_history, value_history + 1, (value_history_length - 1) * sizeof(uint16_t));
for (uint8_t i = 0; i < 4; i++) {
value += analogRead(SWEET_SPOT_LEFT_PIN);
value += analogRead(SWEET_SPOT_RIGHT_PIN);
delayMicroseconds(20);
}
value /= 8;
value_history[value_history_length - 1] = value;
int16_t max_val = -10000;
int16_t min_val = 10000;
for (uint16_t i = 0; i < value_history_length; i++) {
if (value_history[i] > max_val) {
max_val = value_history[i];
}
if (value_history[i] < min_val) {
min_val = value_history[i];
}
}
int16_t center_value = (max_val + min_val) >> 1;
int16_t current_value = value_history[value_history_length - 1];
bool cross = false;
if (current_value > center_value) {
if (current_state != HIGH) {
current_state = HIGH;
cross = true;
}
} else if (current_value <= center_value) {
if (current_state != LOW) {
current_state = LOW;
cross = true;
}
}
if (cross == true) {
int32_t pulse_duration = t_now - last_cross;
//Serial.print("LEN: ");
uint8_t new_bit = round_binary(pulse_duration, 1 * 16666, 5 * 16666);
// Shift array left
memmove(bit_history, bit_history + 1, (24 - 1) * sizeof(uint8_t));
bit_history[24 - 1] = new_bit;
for (uint8_t i = 0; i < 32; i++) {
//Serial.print(bit_history[i]);
}
//Serial.println();
if (current_packet_state == WAITING) {
check_for_header();
if (t_now - wait_start >= 3000000) {
wait_start = t_now;
target_color = standby_color;
target_width = 10;
}
} else if (current_packet_state == PACKET_LENGTH_RX) {
if (bit_rx_count < 8) {
bit_rx_count++;
}
if (bit_rx_count == 8) {
for (uint8_t b = 0; b < 8; b++) {
bitWrite(packet_length, 7 - b, bit_history[24 - 1 - (7 - b)]);
}
Serial.print("LEN: ");
Serial.println(packet_length);
current_packet_state = PACKET_DATA_RX;
bit_rx_count = 0;
chars_rx = 0;
}
} else if (current_packet_state == PACKET_DATA_RX) {
bitWrite(packet_contents[packet_byte_index], 7 - packet_bit_index, new_bit);
packet_bit_index++;
bit_rx_count++;
if (packet_bit_index >= 8) {
packet_bit_index = 0;
packet_byte_index++;
target_color = { 64, 16, 0 };
target_width = 15 + (45 * (packet_byte_index / float(packet_length)));
if (packet_byte_index >= packet_length) {
calculated_CRC8 = calc_crc8(packet_contents, packet_length);
Serial.print("DATA: ");
for (uint8_t i = 0; i < packet_length; i++) {
Serial.print(char(packet_contents[i]));
}
Serial.println();
Serial.print("PACKET: ");
for (uint8_t i = 0; i < packet_length; i++) {
Serial.print(packet_contents[i]);
Serial.print(',');
}
Serial.println();
current_packet_state = PACKET_CRC8_RX;
}
}
} else if (current_packet_state == PACKET_CRC8_RX) {
bitWrite(packet_CRC8, 7 - packet_bit_index, new_bit);
packet_bit_index++;
if (packet_bit_index >= 8) {
packet_bit_index = 0;
Serial.print("RCVD CRC8: ");
Serial.println(packet_CRC8);
Serial.print("CALC CRC8: ");
Serial.println(calculated_CRC8);
if (packet_CRC8 == calculated_CRC8) {
target_color = { 4, 64, 4 };
target_width = 20;
} else {
target_color = { 64, 0, 0 };
target_width = 20;
current_amplitude = 2.0;
}
wait_start = t_now;
current_packet_state = WAITING;
current_state = LOW;
memset(bit_history, 0, 24);
memset(bit_history_ascii, 0, 3);
bit_rx_count = 0;
packet_length = 0;
chars_rx = 0;
memset(packet_contents, 0, 256);
packet_bit_index = 0;
packet_byte_index = 0;
packet_CRC8 = 0;
calculated_CRC8 = 0;
}
}
last_cross = t_now;
}
}
void draw_LED_fade(CRGBF color, float position, float width) {
memset(leds, 0, sizeof(CRGB) * NUM_LEDS);
float middle_led = position * NUM_LEDS;
for (uint8_t i = 0; i < NUM_LEDS; i++) {
float dist = fabs(i - middle_led);
if (dist < width) {
float brightness = (1.0 - (dist / width)) * breath_output;
CRGB out_col;
out_col.r = color.r * brightness;
out_col.g = color.g * brightness;
out_col.b = color.b * brightness;
leds[i] = out_col;
}
}
FastLED.show();
}
void check_for_header() {
for (uint8_t c = 0; c < 3; c++) {
for (uint8_t b = 0; b < 8; b++) {
bitWrite(bit_history_ascii[c], 7 - b, bit_history[8 * c + b]);
}
}
if (bit_history_ascii[0] == 'S') {
if (bit_history_ascii[1] == 'B') {
if (bit_history_ascii[2] == 'F') {
Serial.println("RX");
target_color = { 64, 16, 0 };
target_width = 15;
current_amplitude = 0.5;
current_packet_state = PACKET_LENGTH_RX;
bit_rx_count = 0;
wait_start = t_now;
}
}
}
else if (bit_history_ascii[1] == 'S') {
if (bit_history_ascii[2] == 'B') {
target_color = { 64, 16, 0 };
target_width = 5;
current_amplitude = 0.5;
bit_rx_count = 0;
wait_start = t_now;
}
}
else if (bit_history_ascii[2] == 'S') {
target_color = { 64, 16, 0 };
target_width = 1;
current_amplitude = 0.5;
bit_rx_count = 0;
wait_start = t_now;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment