Created
December 20, 2023 02:28
-
-
Save connornishijima/33c9c7a16fa37c2f84eff7598828dce2 to your computer and use it in GitHub Desktop.
SB Light Transfer Receiver
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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