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

master
Ondřej Hruška 8 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"?>
<!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>
<data>
<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%],
"stats": %stats%,
"success": %success%
}

File diff suppressed because one or more lines are too long

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

@ -87,7 +87,7 @@ var page_waveform = (function () {
return;
}
buildChart(json.samples, 'Sample Nr.', 'ADC value');
buildChart(json.samples, 'Sample number', 'Current - mA');
}
wfm.init = function() {
@ -101,7 +101,7 @@ var page_waveform = (function () {
var freq = $('#freq').val();
//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);

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);
#ifndef DEMO_MODE
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
}
httpdRedirect(connData, "/wifi");

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

@ -1,7 +1,7 @@
#include <esp8266.h>
/* 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--) {
char c = s[i];
@ -11,7 +11,7 @@ static void str_reverse(char *s)
}
/* 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;
@ -30,7 +30,7 @@ int my_itoa(int n, char *s)
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
float rounder = 0.5;

@ -2,6 +2,7 @@
#include <httpd.h>
#include "page_waveform.h"
#include "ftoa.h"
#include "sampling.h"
#include "serial.h"
#include "payload_parser.h"
@ -17,9 +18,24 @@ typedef struct {
} 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)
{
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;
tplReadSamplesJSON_state *st = *arg;
@ -37,53 +53,59 @@ int FLASH_FN tplWaveformJSON(HttpdConnData *connData, char *token, void **arg)
// check how many samples are requested
uint16_t count = 1;
len = httpdFindArg(connData->getArgs, "n", buff20, sizeof(buff20));
if (len != -1) count = (uint16_t)atoi(buff20);
if (count > 4096) {
warn("Requested %d samples, capping at 4096.", count);
count = 4096;
len = httpdFindArg(connData->getArgs, "n", buff, sizeof(buff));
if (len != -1) count = (uint16_t)atoi(buff);
if (count == 0) {
error("Count == 0");
st->success = false;
return HTTPD_CGI_DONE;
}
uint32_t freq = 4096;
len = httpdFindArg(connData->getArgs, "fs", buff20, sizeof(buff20));
if (len != -1) freq = (uint32_t)atoi(buff20);
if (freq > 5000000) {
warn("Requested fs %d Hz, capping at 5 MHz.", freq);
freq = 5000000;
}
uint32_t freq = 0;
len = httpdFindArg(connData->getArgs, "fs", buff, sizeof(buff));
if (len != -1) freq = (uint32_t)atoi(buff);
if (freq == 0) {
error("Requested fs 0 Hz, using 1 Hz");
freq = 1;
error("Freq == 0");
st->success = false;
return HTTPD_CGI_DONE;
}
st->total_count = count;
st->done_count = 0;
st->success = true; // success true by default
// REQUEST THE DATA
meas_request_data(count, freq);
// --- REQUEST THE DATA ---
// 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,
// so if readout fails, success can be set to false.
if (strcmp(token, "values") == 0) {
if (!st->success) {
// failed to start sampling
return HTTPD_CGI_DONE;
}
// Wait for a chunk
uint8_t *chunk = NULL;
uint16_t chunk_len = 0;
// 5 secs or 500 ms
for (int i = 0; i < (st->done_count == 0 ? 5000*100: 500*100); i++) {
// 10 secs or 100 ms - longer wait for intial data.
for (int i = 0; i < (st->done_count == 0 ? SAMPLING_TMEO*100: SAMP_READOUT_TMEO*100); i++) {
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
system_soft_wdt_feed();
system_soft_wdt_feed(); // Feed the dog, or it'll bite.
}
chunk = meas_get_chunk(&chunk_len);
if (!chunk) {
if (chunk == NULL) {
// abort, proceed to the next field.
meas_close();
st->success = false;
@ -99,15 +121,13 @@ int FLASH_FN tplWaveformJSON(HttpdConnData *connData, char *token, void **arg)
httpdSend(connData, ", ", 2);
}
uint32_t samp = pp_u32(&pp);
// print the number
os_sprintf(buff20, "%d", samp);
httpdSend(connData, buff20, -1);
float samp = pp_float(&pp);
my_ftoa(buff, samp, 3);
httpdSend(connData, buff, -1);
}
// wait for more in this substitution
if (st->done_count < st->total_count) {
// wait for more data in this % tag
if (!meas_is_last_chunk()) {
meas_request_next_chunk();
return HTTPD_CGI_MORE; // more numbers to come
} else {
@ -116,6 +136,46 @@ int FLASH_FN tplWaveformJSON(HttpdConnData *connData, char *token, void **arg)
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) {
// success status
httpdSend(connData, (st->success ? "true" : "false"), -1);

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

@ -42,11 +42,14 @@ HttpdBuiltInUrl builtInUrls[] = {
// System Status page
ROUTE_TPL_FILE("/", tplSystemStatus, "/pages/status.tpl"),
ROUTE_TPL_FILE("/status", tplSystemStatus, "/pages/status.tpl"),
ROUTE_TPL_FILE("/api/status.json", tplSystemStatus, "/json/status.tpl"),
// Waveform page
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/fft.json", tplFourierJSON, "/json/samples.tpl"),
// --- WiFi config ---

@ -11,25 +11,33 @@
// the FIFO has 128 bytes, and should accomodate ideally the whole frame.
#define CHUNK_LEN 100
#define SAMPLING_TMEO 10000
#define READOUT_TMEO 100
// Only one readout can happen at a time.
static struct {
bool pending; /*!< Flag that data is currently being read */
uint16_t sesn; /*!< SBMP session of the readout sequence */
bool chunk_ready; /*!< Chunk was received and is ready for reading */
bool pending; /*!< Flag that data is currently being read */
uint16_t sesn; /*!< SBMP session of the readout sequence */
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 */
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
uint32_t pos;
uint32_t total;
ETSTimer abortTimer;
MEAS_FORMAT format; /*!< Requested data format */
// --- data stats ---
MeasStats stats;
} rd;
bool FLASH_FN meas_is_closed(void)
{
return !rd.pending;
}
// --- timeout ---
static void FLASH_FN abortTimerCb(void *arg)
@ -40,13 +48,8 @@ static void FLASH_FN abortTimerCb(void *arg)
// try to abort the readout
sbmp_bulk_abort(dlnk_ep, rd.sesn);
// free the data obj if not NULL
sbmp_ep_free_listener_obj(dlnk_ep, rd.sesn);
// 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;
// release resources and stop
meas_close();
}
static void FLASH_FN setReadoutTmeoTimer(int ms)
@ -111,6 +114,8 @@ bool FLASH_FN meas_is_last_chunk(void)
/** Terminate the readout. */
void FLASH_FN meas_close(void)
{
if (!rd.pending) return; // ignore this call
sbmp_ep_remove_listener(dlnk_ep, rd.sesn);
stopReadoutTmeoTimer();
rd.pending = false;
@ -118,6 +123,10 @@ void FLASH_FN meas_close(void)
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)
{
@ -129,7 +138,6 @@ static void FLASH_FN request_data_sesn_listener(SBMP_Endpoint *ep, SBMP_Datagram
switch (dg->type) {
case DG_BULK_OFFER:// Data ready notification
stopReadoutTmeoTimer();
// info("--- Peer offers data for bulk transfer ---");
// data is ready to be read
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.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
setReadoutTmeoTimer(READOUT_TMEO);
setReadoutTmeoTimer(SAMP_READOUT_TMEO);
// request first chunk
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
stopReadoutTmeoTimer();
// info("--- Received a chunk, length %d ---", dg->length);
// Process the received data
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
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) {
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;
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) {
error("Acquire request already in progress.");
@ -199,17 +218,20 @@ bool FLASH_FN meas_request_data(uint16_t count, uint32_t freq)
return false;
}
// clean up
rd.chunk_ready = false;
rd.pos = 0;
rd.total = 0;
rd.pending = true;
rd.format = format;
memset(&rd.stats, 0, sizeof(MeasStats)); // clear the stats obj
// start the abort timer - timeout
setReadoutTmeoTimer(SAMPLING_TMEO);
// start a message
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;
// register the session listener
@ -237,38 +259,3 @@ fail:
rd.pending = 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 <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
@ -18,7 +36,7 @@
*/
/** 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 */
void meas_request_next_chunk(void);
@ -26,6 +44,12 @@ void meas_request_next_chunk(void);
/** Check if chunk ready to be read */
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.
*

Loading…
Cancel
Save