working fft api, various improvements in sampling, added sampling stats..

master
Ondřej Hruška 9 years ago
parent b183ab7868
commit 8cb31aaa1c
  1. 2
      esp_meas.pro.user
  2. 2
      html/css/app.css
  3. 4
      html/js/all.js
  4. 1
      html/json/samples.tpl
  5. 1231
      html_src/css/app.css
  6. 2
      html_src/js-src/lib/chartist.js
  7. 4
      html_src/js-src/page_waveform.js
  8. 5519
      html_src/js/all.js
  9. 2
      html_src/js/all.js.map
  10. 2
      libesphttpd/util/cgiwifi.c
  11. 3
      user/datalink.h
  12. 6
      user/ftoa.c
  13. 120
      user/page_waveform.c
  14. 2
      user/page_waveform.h
  15. 3
      user/routes.c
  16. 101
      user/sampling.c
  17. 26
      user/sampling.h

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject> <!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 3.6.1, 2016-03-29T02:35:32. --> <!-- Written by QtCreator 3.6.1, 2016-03-29T21:45:36. -->
<qtcreator> <qtcreator>
<data> <data>
<variable>EnvironmentId</variable> <variable>EnvironmentId</variable>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,4 +1,5 @@
{ {
"samples": [%values%], "samples": [%values%],
"stats": %stats%,
"success": %success% "success": %success%
} }

File diff suppressed because one or more lines are too long

@ -1447,7 +1447,7 @@ var Chartist = {
ys[i + 1], ys[i + 1],
false, false,
valueData[i] valueData[i + 1] // changed as per patch on github
); );
} }

@ -87,7 +87,7 @@ var page_waveform = (function () {
return; return;
} }
buildChart(json.samples, 'Sample Nr.', 'ADC value'); buildChart(json.samples, 'Sample number', 'Current - mA');
} }
wfm.init = function() { wfm.init = function() {
@ -101,7 +101,7 @@ var page_waveform = (function () {
var freq = $('#freq').val(); var freq = $('#freq').val();
//http://192.168.1.13 //http://192.168.1.13
$().get('http://192.168.1.13/api/raw.json?n='+samples+'&fs='+freq, onRxData, true, true); $().get('/api/raw.json?n='+samples+'&fs='+freq, onRxData, true, true);
} }
$('#load').on('click', clickHdl); $('#load').on('click', clickHdl);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -254,7 +254,7 @@ int ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) {
info("cgiWifiSetMode: %s", buff); info("cgiWifiSetMode: %s", buff);
#ifndef DEMO_MODE #ifndef DEMO_MODE
wifi_set_opmode(atoi(buff)); wifi_set_opmode(atoi(buff));
system_restart(); system_restart(); // FIXME we should do this in a timer task, so the browser gets a response.
#endif #endif
} }
httpdRedirect(connData, "/wifi"); httpdRedirect(connData, "/wifi");

@ -5,7 +5,8 @@
#include <sbmp.h> #include <sbmp.h>
// request to capture data... // request to capture data...
#define DG_REQUEST_CAPTURE 40 #define DG_REQUEST_RAW 40
#define DG_REQUEST_FFT 41
extern SBMP_Endpoint *dlnk_ep; extern SBMP_Endpoint *dlnk_ep;

@ -1,7 +1,7 @@
#include <esp8266.h> #include <esp8266.h>
/* reverse: reverse string s in place */ /* reverse: reverse string s in place */
static void str_reverse(char *s) static FLASH_FN void str_reverse(char *s)
{ {
for (int i = 0, j = (int)(strlen(s) - 1); i < j; i++, j--) { for (int i = 0, j = (int)(strlen(s) - 1); i < j; i++, j--) {
char c = s[i]; char c = s[i];
@ -11,7 +11,7 @@ static void str_reverse(char *s)
} }
/* itoa: convert n to characters in s. returns length */ /* itoa: convert n to characters in s. returns length */
int my_itoa(int n, char *s) int FLASH_FN my_itoa(int n, char *s)
{ {
int i, sign; int i, sign;
@ -30,7 +30,7 @@ int my_itoa(int n, char *s)
return i; return i;
} }
int my_ftoa(char *buffer, float f, int precision) int FLASH_FN my_ftoa(char *buffer, float f, int precision)
{ {
// add 0.5 * 10^-precision // add 0.5 * 10^-precision
float rounder = 0.5; float rounder = 0.5;

@ -2,6 +2,7 @@
#include <httpd.h> #include <httpd.h>
#include "page_waveform.h" #include "page_waveform.h"
#include "ftoa.h"
#include "sampling.h" #include "sampling.h"
#include "serial.h" #include "serial.h"
#include "payload_parser.h" #include "payload_parser.h"
@ -17,9 +18,24 @@ typedef struct {
} tplReadSamplesJSON_state; } tplReadSamplesJSON_state;
static int FLASH_FN tplSamplesJSON(MEAS_FORMAT fmt, HttpdConnData *connData, char *token, void **arg);
int FLASH_FN tplWaveformJSON(HttpdConnData *connData, char *token, void **arg) int FLASH_FN tplWaveformJSON(HttpdConnData *connData, char *token, void **arg)
{ {
char buff20[20]; return tplSamplesJSON(RAW, connData, token, arg);
}
int FLASH_FN tplFourierJSON(HttpdConnData *connData, char *token, void **arg)
{
return tplSamplesJSON(FFT, connData, token, arg);
}
static int FLASH_FN tplSamplesJSON(MEAS_FORMAT fmt, HttpdConnData *connData, char *token, void **arg)
{
char buff[128];
int len; int len;
tplReadSamplesJSON_state *st = *arg; tplReadSamplesJSON_state *st = *arg;
@ -37,53 +53,59 @@ int FLASH_FN tplWaveformJSON(HttpdConnData *connData, char *token, void **arg)
// check how many samples are requested // check how many samples are requested
uint16_t count = 1; uint16_t count = 1;
len = httpdFindArg(connData->getArgs, "n", buff20, sizeof(buff20)); len = httpdFindArg(connData->getArgs, "n", buff, sizeof(buff));
if (len != -1) count = (uint16_t)atoi(buff20); if (len != -1) count = (uint16_t)atoi(buff);
if (count > 4096) { if (count == 0) {
warn("Requested %d samples, capping at 4096.", count); error("Count == 0");
count = 4096; st->success = false;
return HTTPD_CGI_DONE;
} }
uint32_t freq = 4096; uint32_t freq = 0;
len = httpdFindArg(connData->getArgs, "fs", buff20, sizeof(buff20)); len = httpdFindArg(connData->getArgs, "fs", buff, sizeof(buff));
if (len != -1) freq = (uint32_t)atoi(buff20); if (len != -1) freq = (uint32_t)atoi(buff);
if (freq > 5000000) {
warn("Requested fs %d Hz, capping at 5 MHz.", freq);
freq = 5000000;
}
if (freq == 0) { if (freq == 0) {
error("Requested fs 0 Hz, using 1 Hz"); error("Freq == 0");
freq = 1; st->success = false;
return HTTPD_CGI_DONE;
} }
st->total_count = count; st->total_count = count;
st->done_count = 0; st->done_count = 0;
st->success = true; // success true by default
// REQUEST THE DATA // --- REQUEST THE DATA ---
meas_request_data(count, freq); // This actually asks the STM32 over SBMP to start the ADC DMA,
// and we'll wait for the data to arrive.
st->success = meas_request_data(fmt, count, freq);
if (!st->success) {
error("Failed to start sampling");
}
} }
// the "success" field is after the data, // the "success" field is after the data,
// so if readout fails, success can be set to false. // so if readout fails, success can be set to false.
if (strcmp(token, "values") == 0) { if (strcmp(token, "values") == 0) {
if (!st->success) {
// failed to start sampling
return HTTPD_CGI_DONE;
}
// Wait for a chunk // Wait for a chunk
uint8_t *chunk = NULL; uint8_t *chunk = NULL;
uint16_t chunk_len = 0; uint16_t chunk_len = 0;
// 5 secs or 500 ms // 10 secs or 100 ms - longer wait for intial data.
for (int i = 0; i < (st->done_count == 0 ? 5000*100: 500*100); i++) { for (int i = 0; i < (st->done_count == 0 ? SAMPLING_TMEO*100: SAMP_READOUT_TMEO*100); i++) {
uart_poll(); uart_poll();
if (meas_chunk_ready()) break; if (meas_chunk_ready() || meas_is_closed()) break; // We have some data! --- or transaction aborted by peer :(
os_delay_us(10); // 1 ms os_delay_us(10); // 1 ms
system_soft_wdt_feed(); system_soft_wdt_feed(); // Feed the dog, or it'll bite.
} }
chunk = meas_get_chunk(&chunk_len); chunk = meas_get_chunk(&chunk_len);
if (!chunk) { if (chunk == NULL) {
// abort, proceed to the next field. // abort, proceed to the next field.
meas_close(); meas_close();
st->success = false; st->success = false;
@ -99,15 +121,13 @@ int FLASH_FN tplWaveformJSON(HttpdConnData *connData, char *token, void **arg)
httpdSend(connData, ", ", 2); httpdSend(connData, ", ", 2);
} }
uint32_t samp = pp_u32(&pp); float samp = pp_float(&pp);
my_ftoa(buff, samp, 3);
// print the number httpdSend(connData, buff, -1);
os_sprintf(buff20, "%d", samp);
httpdSend(connData, buff20, -1);
} }
// wait for more in this substitution // wait for more data in this % tag
if (st->done_count < st->total_count) { if (!meas_is_last_chunk()) {
meas_request_next_chunk(); meas_request_next_chunk();
return HTTPD_CGI_MORE; // more numbers to come return HTTPD_CGI_MORE; // more numbers to come
} else { } else {
@ -116,6 +136,46 @@ int FLASH_FN tplWaveformJSON(HttpdConnData *connData, char *token, void **arg)
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
} else if (strcmp(token, "stats") == 0) {
// the STATS json block
if (!st->success) {
httpdSend(connData, "null", 4);
} else {
// no %f in sprintf :(
MeasStats *stats = meas_get_stats();
httpdSend(connData, "{", 1);
sprintf(buff, "\"count\": %d, ", stats->count);
httpdSend(connData, buff, -1);
httpdSend(connData, "\"freq\": ", -1);
my_ftoa(buff, stats->freq, 3);
httpdSend(connData, buff, -1);
httpdSend(connData, ", \"min\": ", -1);
my_ftoa(buff, stats->min, 3);
httpdSend(connData, buff, -1);
httpdSend(connData, ", \"max\": ", -1);
my_ftoa(buff, stats->max, 3);
httpdSend(connData, buff, -1);
httpdSend(connData, ", \"rms\": ", -1);
my_ftoa(buff, stats->rms, 3);
httpdSend(connData, buff, -1);
if (fmt == FFT) {
// ... maybe something special for fft ...
}
sprintf(buff, ", \"format\": \"%s\"", fmt == RAW ? "RAW" : fmt == FFT ? "FFT" : "UNKNOWN");
httpdSend(connData, buff, -1);
httpdSend(connData, "}", 1);
}
} else if (strcmp(token, "success") == 0) { } else if (strcmp(token, "success") == 0) {
// success status // success status
httpdSend(connData, (st->success ? "true" : "false"), -1); httpdSend(connData, (st->success ? "true" : "false"), -1);

@ -5,4 +5,6 @@
int tplWaveformJSON(HttpdConnData *connData, char *token, void **arg); int tplWaveformJSON(HttpdConnData *connData, char *token, void **arg);
int tplFourierJSON(HttpdConnData *connData, char *token, void **arg);
#endif // PAGE_WAVEFORM_H #endif // PAGE_WAVEFORM_H

@ -42,11 +42,14 @@ HttpdBuiltInUrl builtInUrls[] = {
// System Status page // System Status page
ROUTE_TPL_FILE("/", tplSystemStatus, "/pages/status.tpl"), ROUTE_TPL_FILE("/", tplSystemStatus, "/pages/status.tpl"),
ROUTE_TPL_FILE("/status", tplSystemStatus, "/pages/status.tpl"), ROUTE_TPL_FILE("/status", tplSystemStatus, "/pages/status.tpl"),
ROUTE_TPL_FILE("/api/status.json", tplSystemStatus, "/json/status.tpl"), ROUTE_TPL_FILE("/api/status.json", tplSystemStatus, "/json/status.tpl"),
// Waveform page // Waveform page
ROUTE_FILE("/waveform", "/pages/waveform.html"), // static file, html -> can use gzip ROUTE_FILE("/waveform", "/pages/waveform.html"), // static file, html -> can use gzip
ROUTE_TPL_FILE("/api/raw.json", tplWaveformJSON, "/json/samples.tpl"), ROUTE_TPL_FILE("/api/raw.json", tplWaveformJSON, "/json/samples.tpl"),
ROUTE_TPL_FILE("/api/fft.json", tplFourierJSON, "/json/samples.tpl"),
// --- WiFi config --- // --- WiFi config ---

@ -11,25 +11,33 @@
// the FIFO has 128 bytes, and should accomodate ideally the whole frame. // the FIFO has 128 bytes, and should accomodate ideally the whole frame.
#define CHUNK_LEN 100 #define CHUNK_LEN 100
#define SAMPLING_TMEO 10000
#define READOUT_TMEO 100
// Only one readout can happen at a time. // Only one readout can happen at a time.
static struct { static struct {
bool pending; /*!< Flag that data is currently being read */ bool pending; /*!< Flag that data is currently being read */
uint16_t sesn; /*!< SBMP session of the readout sequence */ uint16_t sesn; /*!< SBMP session of the readout sequence */
bool chunk_ready; /*!< Chunk was received and is ready for reading */ bool chunk_ready; /*!< Chunk was received and is ready for reading */
uint8_t received_chunk[CHUNK_LEN]; /*!< Copy of the latest received chunk of data */ uint8_t received_chunk[CHUNK_LEN]; /*!< Copy of the latest received chunk of data */
uint16_t received_chunk_size; /*!< Size of the chunk in latest_chunk_copy */ uint16_t received_chunk_size; /*!< Size of the chunk in latest_chunk_copy */
// the readout state // the readout state
uint32_t pos; uint32_t pos;
uint32_t total; uint32_t total;
ETSTimer abortTimer; ETSTimer abortTimer;
MEAS_FORMAT format; /*!< Requested data format */
// --- data stats ---
MeasStats stats;
} rd; } rd;
bool FLASH_FN meas_is_closed(void)
{
return !rd.pending;
}
// --- timeout --- // --- timeout ---
static void FLASH_FN abortTimerCb(void *arg) static void FLASH_FN abortTimerCb(void *arg)
@ -40,13 +48,8 @@ static void FLASH_FN abortTimerCb(void *arg)
// try to abort the readout // try to abort the readout
sbmp_bulk_abort(dlnk_ep, rd.sesn); sbmp_bulk_abort(dlnk_ep, rd.sesn);
// free the data obj if not NULL // release resources and stop
sbmp_ep_free_listener_obj(dlnk_ep, rd.sesn); meas_close();
// release the slot
sbmp_ep_remove_listener(dlnk_ep, rd.sesn);
// invalidate the chunk buffer and indicate that a new readout can start
rd.pending = false;
} }
static void FLASH_FN setReadoutTmeoTimer(int ms) static void FLASH_FN setReadoutTmeoTimer(int ms)
@ -111,6 +114,8 @@ bool FLASH_FN meas_is_last_chunk(void)
/** Terminate the readout. */ /** Terminate the readout. */
void FLASH_FN meas_close(void) void FLASH_FN meas_close(void)
{ {
if (!rd.pending) return; // ignore this call
sbmp_ep_remove_listener(dlnk_ep, rd.sesn); sbmp_ep_remove_listener(dlnk_ep, rd.sesn);
stopReadoutTmeoTimer(); stopReadoutTmeoTimer();
rd.pending = false; rd.pending = false;
@ -118,6 +123,10 @@ void FLASH_FN meas_close(void)
info("Transfer closed."); info("Transfer closed.");
} }
MeasStats FLASH_FN *meas_get_stats(void)
{
return &rd.stats;
}
static void FLASH_FN request_data_sesn_listener(SBMP_Endpoint *ep, SBMP_Datagram *dg, void **obj) static void FLASH_FN request_data_sesn_listener(SBMP_Endpoint *ep, SBMP_Datagram *dg, void **obj)
{ {
@ -129,7 +138,6 @@ static void FLASH_FN request_data_sesn_listener(SBMP_Endpoint *ep, SBMP_Datagram
switch (dg->type) { switch (dg->type) {
case DG_BULK_OFFER:// Data ready notification case DG_BULK_OFFER:// Data ready notification
stopReadoutTmeoTimer(); stopReadoutTmeoTimer();
// info("--- Peer offers data for bulk transfer ---");
// data is ready to be read // data is ready to be read
pp = pp_start(dg->payload, dg->length); pp = pp_start(dg->payload, dg->length);
@ -137,10 +145,20 @@ static void FLASH_FN request_data_sesn_listener(SBMP_Endpoint *ep, SBMP_Datagram
rd.pos = 0; rd.pos = 0;
rd.total = pp_u32(&pp); rd.total = pp_u32(&pp);
// dbg("Total bytes avail: %d", rd.total); // --- here start the user data (common) ---
rd.stats.count = pp_u32(&pp);
rd.stats.freq = pp_float(&pp);
rd.stats.min = pp_float(&pp);
rd.stats.max = pp_float(&pp);
rd.stats.rms = pp_float(&pp);
// --- user data end ---
if (rd.format == FFT) {
// TODO read extra FFT stats
}
// renew the timeout // renew the timeout
setReadoutTmeoTimer(READOUT_TMEO); setReadoutTmeoTimer(SAMP_READOUT_TMEO);
// request first chunk // request first chunk
sbmp_bulk_request(ep, rd.pos, CHUNK_LEN, dg->session); sbmp_bulk_request(ep, rd.pos, CHUNK_LEN, dg->session);
@ -148,7 +166,6 @@ static void FLASH_FN request_data_sesn_listener(SBMP_Endpoint *ep, SBMP_Datagram
case DG_BULK_DATA: // data received case DG_BULK_DATA: // data received
stopReadoutTmeoTimer(); stopReadoutTmeoTimer();
// info("--- Received a chunk, length %d ---", dg->length);
// Process the received data // Process the received data
memcpy(rd.received_chunk, dg->payload, dg->length); memcpy(rd.received_chunk, dg->payload, dg->length);
@ -158,7 +175,9 @@ static void FLASH_FN request_data_sesn_listener(SBMP_Endpoint *ep, SBMP_Datagram
// move the pointer for next request // move the pointer for next request
rd.pos += dg->length; rd.pos += dg->length;
setReadoutTmeoTimer(READOUT_TMEO); // timeout to retrieve the data & ask for more setReadoutTmeoTimer(SAMP_READOUT_TMEO); // timeout to retrieve the data & ask for more
// --- Now we wait for the CGI func to retrieve the chunk and send it to the browser. ---
if (rd.pos >= rd.total) { if (rd.pos >= rd.total) {
info("Transfer is complete."); info("Transfer is complete.");
@ -183,11 +202,11 @@ cleanup:
} }
bool FLASH_FN meas_request_data(uint16_t count, uint32_t freq) bool FLASH_FN meas_request_data(MEAS_FORMAT format, uint16_t count, uint32_t freq)
{ {
bool suc = false; bool suc = false;
info("Requesting data capture - %d samples @ %d Hz.", count, freq); info("Requesting data capture - %d samples @ %d Hz, fmt %d.", count, freq, format);
if (rd.pending) { if (rd.pending) {
error("Acquire request already in progress."); error("Acquire request already in progress.");
@ -199,17 +218,20 @@ bool FLASH_FN meas_request_data(uint16_t count, uint32_t freq)
return false; return false;
} }
// clean up
rd.chunk_ready = false; rd.chunk_ready = false;
rd.pos = 0; rd.pos = 0;
rd.total = 0; rd.total = 0;
rd.pending = true; rd.pending = true;
rd.format = format;
memset(&rd.stats, 0, sizeof(MeasStats)); // clear the stats obj
// start the abort timer - timeout // start the abort timer - timeout
setReadoutTmeoTimer(SAMPLING_TMEO); setReadoutTmeoTimer(SAMPLING_TMEO);
// start a message // start a message
uint16_t sesn = 0; uint16_t sesn = 0;
suc = sbmp_ep_start_message(dlnk_ep, DG_REQUEST_CAPTURE, sizeof(uint16_t)+sizeof(uint32_t), &sesn); suc = sbmp_ep_start_message(dlnk_ep, format, sizeof(uint16_t)+sizeof(uint32_t), &sesn); // format enum matches the message types
if (!suc) goto fail; if (!suc) goto fail;
// register the session listener // register the session listener
@ -237,38 +259,3 @@ fail:
rd.pending = false; rd.pending = false;
return false; return false;
} }
// ------ C G I ---------
/*
int FLASH_FN cgiReadSamples(HttpdConnData *connData)
{
char buff[128];
if (connData->conn == NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
uint16_t count = 1;
int len = httpdFindArg(connData->getArgs, "n", buff, sizeof(buff));
if (len != -1) {
count = (uint16_t)atoi(buff);
}
dbg("User wants %d samples.", count);
meas_request_data(count);
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
// body
httpdSend(connData, "OK.", -1);
return HTTPD_CGI_DONE;
}
*/

@ -3,6 +3,24 @@
#include <esp8266.h> #include <esp8266.h>
#include <httpd.h> #include <httpd.h>
#include "datalink.h"
#define SAMPLING_TMEO 6000
#define SAMP_READOUT_TMEO 100
typedef struct {
uint32_t count;
float freq; // actual frequency - not exact due to the prescaller limitations
float min;
float max;
float rms;
} MeasStats;
typedef enum {
RAW = DG_REQUEST_RAW, // same as the SBMP packet numbers used to request it
FFT = DG_REQUEST_FFT
} MEAS_FORMAT;
/** /**
* Reading procedure * Reading procedure
@ -18,7 +36,7 @@
*/ */
/** Request data from the sampling module. Count - number of samples. */ /** Request data from the sampling module. Count - number of samples. */
bool meas_request_data(uint16_t count, uint32_t freq); // TODO specify what kind of data - currently direct samples. bool meas_request_data(MEAS_FORMAT format, uint16_t count, uint32_t freq); // TODO specify what kind of data - currently direct samples.
/** request next chunk */ /** request next chunk */
void meas_request_next_chunk(void); void meas_request_next_chunk(void);
@ -26,6 +44,12 @@ void meas_request_next_chunk(void);
/** Check if chunk ready to be read */ /** Check if chunk ready to be read */
bool meas_chunk_ready(void); bool meas_chunk_ready(void);
/** Check if closed (if data was expected, this means the peer aborted the transaction) */
bool meas_is_closed(void);
/** Get the stats struct */
MeasStats *meas_get_stats(void);
/** /**
* @brief Get received chunk. NULL if none. * @brief Get received chunk. NULL if none.
* *

Loading…
Cancel
Save