Skip to content

Commit

Permalink
Add support for 64k data over sound
Browse files Browse the repository at this point in the history
  • Loading branch information
rgerganov committed Jun 2, 2023
1 parent e3bfd26 commit 116a71b
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 84 deletions.
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
<div class="col-10">
<div class="form-check">
<input class="form-check-input" type="radio" name="iface" id="ifaceSound" value="sound" checked>
<label class="form-check-label" for="ifaceSound">
<label id="soundLabel" class="form-check-label" for="ifaceSound">
Sound
</label>
</div>
Expand Down
65 changes: 26 additions & 39 deletions docs/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@ function getInput() {
async function repaint() {
let inp = getInput();
inp = await preprocessImages(inp);
//console.log(inp);
let data = encodeInput(inp);
if (data.length > 256) {
let duration = Math.ceil(data.length / 33);
$('#soundLabel').text('Sound (~' + duration + ' sec.)');
} else {
$('#soundLabel').text('Sound');
}
render(inp);
}

Expand Down Expand Up @@ -223,21 +229,6 @@ async function onShare() {
}
}

function changeSize(radio, doRepaint=true)
{
let canvas = document.getElementById("ggCanvas");
if (radio.value == "small") {
canvas.width = 250;
canvas.height = 122;
} else if (radio.value == "large") {
canvas.width = 360;
canvas.height = 240;
}
if (doRepaint) {
repaint();
}
}

function render(input)
{
const canvas = document.getElementById("ggCanvas");
Expand Down Expand Up @@ -373,19 +364,28 @@ async function readSerialOutput(port)
return result;
}

async function programSerial(input)
function encodeInput(input)
{
console.log("Programming over serial port");
let lengthPtr = Module._malloc(4);
let ptr = Module.ccall('encode', 'number', ['string', 'number'], [input, lengthPtr]);
if (ptr == 0) {
Module._free(lengthPtr);
return;
return null;
}
let length = Module.getValue(lengthPtr, 'i32');
console.log("Encoded data length: " + length);
Module._free(lengthPtr);
let data = new Uint8Array(Module.HEAPU8.buffer, ptr, length);
// make a copy and free the memory
let result = new Uint8Array(data);
Module._free(ptr);
return result;
}

async function programSerial(input)
{
console.log("Programming over serial port");
let data = encodeInput(input);
console.log("Encoded data length: " + data.length);
console.log("Encoded data: " + data);

let port = {};
Expand All @@ -407,7 +407,6 @@ async function programSerial(input)
let result = await closedPromise;
await port.close();
console.log("All done, result: " + result);
Module._free(ptr);
}

let ggwave = null;
Expand All @@ -420,20 +419,8 @@ let ggwaveInstance = null;
async function programSound(input)
{
console.log("Programming over sound");
let lengthPtr = Module._malloc(4);
let ptr = Module.ccall('encode', 'number', ['string', 'number'], [input, lengthPtr]);
if (ptr == 0) {
Module._free(lengthPtr);
return;
}
let length = Module.getValue(lengthPtr, 'i32');
console.log("Encoded data length: " + length);
Module._free(lengthPtr);
if (length > 256) {
Module._free(ptr);
throw "The maximum data length when using sound is 256 bytes";
}
let data = new Uint8Array(Module.HEAPU8.buffer, ptr, length);
let data = encodeInput(input);
console.log("Encoded data length: " + data.length);
console.log("Encoded data: " + data);

if (audioContext == null) {
Expand Down Expand Up @@ -481,7 +468,6 @@ async function programSound(input)
tx();
await promise;
console.log("All done");
Module._free(ptr);
}

function bitmapToBase64(bitmap)
Expand All @@ -504,7 +490,6 @@ const loadImage = (url) => new Promise((resolve, reject) => {
async function preprocessImages(input)
{
const canvas = document.getElementById("ggCanvas");
const ctx = canvas.getContext("2d", {willReadFrequently: true});
// find all image escape sequences "\I<x>,<y>,<w>,<h><url>"
let regex = /\\I(\d+),(\d+),(\d+),(\d+),([^\\]+)/g;
let match = null;
Expand All @@ -525,8 +510,10 @@ async function preprocessImages(input)
if (h == 0) {
h = img.height;
}
ctx.drawImage(img, x, y, w, h);
let imgData = ctx.getImageData(x, y, w, h);
let tempCanvas = new OffscreenCanvas(w, h);
let tempCtx = tempCanvas.getContext("2d");
tempCtx.drawImage(img, 0, 0, w, h);
let imgData = tempCtx.getImageData(0, 0, w, h);
let rgbaArr = imgData.data;
let rgbaBuf = Module._malloc(rgbaArr.length*rgbaArr.BYTES_PER_ELEMENT);
if (rgbaBuf == 0) {
Expand Down
109 changes: 65 additions & 44 deletions target/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ volatile int samplesRead = 0;
int16_t sampleBufferCur[BUF_SIZE]; // we read new samples in this buffer
int16_t sampleBuffer[3*SAMPLES_PER_FRAME]; // after that, we queue them in this bigger buffer

GGWave ggwave;
GGWave::TxRxData result;

#if GGTAG_DEEP_SLEEP == 1
// In this mode, we use RTC to wake up from deep sleep every few seconds and check if the BOOTSEL button is pressed

Expand Down Expand Up @@ -247,38 +244,33 @@ void run_from_battery()
}
}

GGWave *ggwave = new GGWave();
GGWave::TxRxData result;
// initialize ggwave
{
ggwave.setLogFile(nullptr);
auto p = GGWave::getDefaultParameters();
p.payloadLength = 16;
p.sampleRateInp = SAMPLE_RATE;
p.sampleRateOut = SAMPLE_RATE;
p.sampleRate = SAMPLE_RATE;
p.samplesPerFrame = SAMPLES_PER_FRAME;
p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16;
p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8;
p.operatingMode = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES;

GGWave::Protocols::tx().disableAll();
GGWave::Protocols::rx().disableAll();
GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_FASTEST, true);
//GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true);
//GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST, true);
GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true);
//GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true);
//GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST, true);
GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true);
//GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_ULTRASOUND_FASTEST, true);
// Print the memory required for the "ggwave" instance:
ggwave.prepare(p, false);
printf("Required memory by the ggwave instance: %d\n", ggwave.heapSize());
if (!ggwave.prepare(p, true)) {
printf("ggwave initialization failed\n");
while (1) { tight_loop_contents(); }
}
printf("ggwave initialized successfully\n");
}
GGWave::setLogFile(nullptr);
auto p = GGWave::getDefaultParameters();
p.payloadLength = 16;
p.sampleRateInp = SAMPLE_RATE;
p.sampleRateOut = SAMPLE_RATE;
p.sampleRate = SAMPLE_RATE;
p.samplesPerFrame = SAMPLES_PER_FRAME;
p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16;
p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8;
p.operatingMode = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES;

GGWave::Protocols::tx().disableAll();
GGWave::Protocols::rx().disableAll();
GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_FASTEST, true);
//GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true);
//GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST, true);
GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true);
//GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true);
//GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST, true);
GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true);
//GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_ULTRASOUND_FASTEST, true);
// Print the memory required for the "ggwave" instance:
ggwave->prepare(p, false);
printf("Required memory by the ggwave instance: %d\n", ggwave->heapSize());

#if GGTAG_DEEP_SLEEP == 1
rtc_init();
Expand All @@ -296,6 +288,14 @@ void run_from_battery()
while (1) {
// init mic and listen for AWAKE_RUN_S seconds
if (listen) {
if (ggwave == NULL) {
ggwave = new GGWave();
}
if (!ggwave->prepare(p, true)) {
printf("ggwave initialization failed\n");
while (1) { tight_loop_contents(); }
}
printf("ggwave initialized successfully\n");
printf("Listening for %d seconds\n", AWAKE_RUN_S);

// initialize the PDM microphone
Expand All @@ -314,12 +314,15 @@ void run_from_battery()
}

int totalSamples = 0;
// listen for AWAKE_RUN_S seconds max unless we receive data
int maxSamples = AWAKE_RUN_S*SAMPLE_RATE;
int niter = 0;
int sampleCount = 0;
int offset = 0;
uint8_t data[256] = {0};
uint8_t *data = NULL;
int dataLength = 0;

while (totalSamples < AWAKE_RUN_S*SAMPLE_RATE) {
while (totalSamples < maxSamples) {
// wait for new samples
while (samplesRead == 0) { tight_loop_contents(); }
// store and clear the samples read from the callback
Expand All @@ -333,7 +336,7 @@ void run_from_battery()
while (sampleCount >= SAMPLES_PER_FRAME) {
auto tStart = to_ms_since_boot(get_absolute_time());
//printf("trying to decode... sampleCount = %d\n", sampleCount);
ggwave.decode(sampleBuffer, SAMPLES_PER_FRAME*sizeof(int16_t));
ggwave->decode(sampleBuffer, SAMPLES_PER_FRAME*sizeof(int16_t));
auto tEnd = to_ms_since_boot(get_absolute_time());
if (++niter % 10 == 0) {
printf("time: %d\n", tEnd - tStart);
Expand All @@ -344,22 +347,33 @@ void run_from_battery()
}

// Check if we have successfully decoded any data:
int nr = ggwave.rxTakeData(result);
int nr = ggwave->rxTakeData(result);
if (nr > 0) {
// check if the received data is different from the previous one
if (memcmp(lastData, result.data(), nr) != 0) {
// extend listening time
maxSamples = totalSamples + AWAKE_RUN_S*SAMPLE_RATE;
printf("Received %d bytes\n", nr);
if (data == NULL) {
// the first two bytes are the total length of the data
int l1 = result.data()[0];
int l2 = result.data()[1];
dataLength = l1*256 + l2;
printf("Data length: %d\n", dataLength);
data = (uint8_t *)malloc(dataLength);
if (data == NULL) {
printf("Failed to allocate memory\n");
break;
}
}
memcpy(data + offset, result.data(), nr);
offset += nr;
memcpy(lastData, result.data(), nr);
}
// the first two bytes are the total length of the data
// but we don't support more than 255 bytes when using sound
int dataLength = data[1];
// if we have received all the data, don't wait for more
if (offset >= dataLength + 2) {
printf("Received all %d bytes\n", dataLength);
totalSamples = AWAKE_RUN_S*SAMPLE_RATE;
maxSamples = totalSamples;
break;
}
}
Expand All @@ -372,14 +386,21 @@ void run_from_battery()
}
}

printf("Deallocate ggwave\n");
delete ggwave;
ggwave = NULL;

// display the data that has been received
if (offset > 0) {
if (dataLength > 0) {
printf("Drawing\n");
EPD_Init();
Paint_NewImage(img, EPD_WIDTH, EPD_HEIGHT, 90, WHITE);
Paint_Clear(WHITE);
renderBits(data+2, data[1]*8);
renderBits(data+2, dataLength*8);
EPD_Display(img, NULL);
free(data);
data = NULL;
dataLength = 0;
// put the display to sleep
EPD_Sleep();
}
Expand Down

0 comments on commit 116a71b

Please sign in to comment.