added no aggregation for no activity, buffer memory swapping
This commit is contained in:
parent
da602ad67b
commit
c15f893e47
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
obj_*
|
||||
*~
|
||||
*~
|
||||
code.pdf
|
197
Coursework-Reports/code.lyx
Normal file
197
Coursework-Reports/code.lyx
Normal file
@ -0,0 +1,197 @@
|
||||
#LyX 2.3 created this file. For more info see http://www.lyx.org/
|
||||
\lyxformat 544
|
||||
\begin_document
|
||||
\begin_header
|
||||
\save_transient_properties true
|
||||
\origin unavailable
|
||||
\textclass article
|
||||
\begin_preamble
|
||||
\usepackage{color}
|
||||
|
||||
\definecolor{commentgreen}{RGB}{0,94,11}
|
||||
\end_preamble
|
||||
\use_default_options true
|
||||
\begin_modules
|
||||
customHeadersFooters
|
||||
\end_modules
|
||||
\maintain_unincluded_children false
|
||||
\language english
|
||||
\language_package default
|
||||
\inputencoding auto
|
||||
\fontencoding global
|
||||
\font_roman "default" "default"
|
||||
\font_sans "default" "default"
|
||||
\font_typewriter "default" "default"
|
||||
\font_math "auto" "auto"
|
||||
\font_default_family default
|
||||
\use_non_tex_fonts false
|
||||
\font_sc false
|
||||
\font_osf false
|
||||
\font_sf_scale 100 100
|
||||
\font_tt_scale 100 100
|
||||
\use_microtype false
|
||||
\use_dash_ligatures true
|
||||
\graphics default
|
||||
\default_output_format default
|
||||
\output_sync 0
|
||||
\bibtex_command default
|
||||
\index_command default
|
||||
\paperfontsize default
|
||||
\spacing single
|
||||
\use_hyperref true
|
||||
\pdf_title "IoT Aggregation Algorithm Coursework"
|
||||
\pdf_author "Andy Pack"
|
||||
\pdf_subject "IoT"
|
||||
\pdf_bookmarks true
|
||||
\pdf_bookmarksnumbered false
|
||||
\pdf_bookmarksopen false
|
||||
\pdf_bookmarksopenlevel 1
|
||||
\pdf_breaklinks true
|
||||
\pdf_pdfborder true
|
||||
\pdf_colorlinks false
|
||||
\pdf_backref false
|
||||
\pdf_pdfusetitle true
|
||||
\papersize default
|
||||
\use_geometry true
|
||||
\use_package amsmath 1
|
||||
\use_package amssymb 1
|
||||
\use_package cancel 1
|
||||
\use_package esint 1
|
||||
\use_package mathdots 1
|
||||
\use_package mathtools 1
|
||||
\use_package mhchem 1
|
||||
\use_package stackrel 1
|
||||
\use_package stmaryrd 1
|
||||
\use_package undertilde 1
|
||||
\cite_engine basic
|
||||
\cite_engine_type default
|
||||
\biblio_style plain
|
||||
\use_bibtopic false
|
||||
\use_indices false
|
||||
\paperorientation portrait
|
||||
\suppress_date false
|
||||
\justification true
|
||||
\use_refstyle 1
|
||||
\use_minted 0
|
||||
\index Index
|
||||
\shortcut idx
|
||||
\color #008000
|
||||
\end_index
|
||||
\leftmargin 1cm
|
||||
\topmargin 1.5cm
|
||||
\rightmargin 1cm
|
||||
\bottommargin 1.5cm
|
||||
\secnumdepth 3
|
||||
\tocdepth 3
|
||||
\paragraph_separation indent
|
||||
\paragraph_indentation default
|
||||
\is_math_indent 0
|
||||
\math_numbering_side default
|
||||
\quotes_style english
|
||||
\dynamic_quotes 0
|
||||
\papercolumns 1
|
||||
\papersides 1
|
||||
\paperpagestyle fancy
|
||||
\tracking_changes false
|
||||
\output_changes false
|
||||
\html_math_output 0
|
||||
\html_css_as_file 0
|
||||
\html_be_strict false
|
||||
\end_header
|
||||
|
||||
\begin_body
|
||||
|
||||
\begin_layout Left Header
|
||||
IoT Aggregation Algorithm Coursework
|
||||
\end_layout
|
||||
|
||||
\begin_layout Left Footer
|
||||
November 2020
|
||||
\end_layout
|
||||
|
||||
\begin_layout Right Footer
|
||||
Andy Pack / 6420013
|
||||
\end_layout
|
||||
|
||||
\begin_layout Standard
|
||||
\begin_inset CommandInset toc
|
||||
LatexCommand lstlistoflistings
|
||||
|
||||
\end_inset
|
||||
|
||||
|
||||
\begin_inset CommandInset include
|
||||
LatexCommand lstinputlisting
|
||||
filename "../Coursework/coursework.c"
|
||||
lstparams "breaklines=true,frame=tb,language=C,basicstyle={\\ttfamily},commentstyle={\\color{commentgreen}\\itshape},keywordstyle={\\color{blue}},emphstyle={\\color{red}},stringstyle={\\color{red}},identifierstyle={\\color{cyan}},caption={Main coursework program: 2 processes for reading and aggregating data}"
|
||||
|
||||
\end_inset
|
||||
|
||||
|
||||
\end_layout
|
||||
|
||||
\begin_layout Standard
|
||||
\begin_inset Newpage pagebreak
|
||||
\end_inset
|
||||
|
||||
|
||||
\end_layout
|
||||
|
||||
\begin_layout Standard
|
||||
\begin_inset CommandInset include
|
||||
LatexCommand lstinputlisting
|
||||
filename "../Coursework/buffer.h"
|
||||
lstparams "breaklines=true,frame=tb,language=C,basicstyle={\\ttfamily},commentstyle={\\color{commentgreen}\\itshape},keywordstyle={\\color{blue}},emphstyle={\\color{red}},stringstyle={\\color{red}},identifierstyle={\\color{cyan}},caption={Buffer header file: get, free and manipulate buffers}"
|
||||
|
||||
\end_inset
|
||||
|
||||
|
||||
\begin_inset Newpage pagebreak
|
||||
\end_inset
|
||||
|
||||
|
||||
\end_layout
|
||||
|
||||
\begin_layout Standard
|
||||
\begin_inset CommandInset include
|
||||
LatexCommand lstinputlisting
|
||||
filename "../Coursework/math.h"
|
||||
lstparams "breaklines=true,frame=tb,language=C,basicstyle={\\ttfamily},commentstyle={\\color{commentgreen}\\itshape},keywordstyle={\\color{blue}},emphstyle={\\color{red}},stringstyle={\\color{red}},identifierstyle={\\color{cyan}},caption={Math header file: mean, standard deviation, implementations of ceil, sqrt}"
|
||||
|
||||
\end_inset
|
||||
|
||||
|
||||
\begin_inset Newpage pagebreak
|
||||
\end_inset
|
||||
|
||||
|
||||
\end_layout
|
||||
|
||||
\begin_layout Standard
|
||||
\begin_inset CommandInset include
|
||||
LatexCommand lstinputlisting
|
||||
filename "../Coursework/io.h"
|
||||
lstparams "breaklines=true,frame=tb,language=C,basicstyle={\\ttfamily},commentstyle={\\color{commentgreen}\\itshape},keywordstyle={\\color{blue}},emphstyle={\\color{red}},stringstyle={\\color{red}},identifierstyle={\\color{cyan}},caption={IO header file: init function for starting sensors}"
|
||||
|
||||
\end_inset
|
||||
|
||||
|
||||
\begin_inset Newpage pagebreak
|
||||
\end_inset
|
||||
|
||||
|
||||
\end_layout
|
||||
|
||||
\begin_layout Standard
|
||||
\begin_inset CommandInset include
|
||||
LatexCommand lstinputlisting
|
||||
filename "../Coursework/util.h"
|
||||
lstparams "breaklines=true,frame=tb,language=C,basicstyle={\\ttfamily},commentstyle={\\color{commentgreen}\\itshape},keywordstyle={\\color{blue}},emphstyle={\\color{red}},stringstyle={\\color{red}},identifierstyle={\\color{cyan}},caption={Other utilities: short and float printing functions from earlier labs}"
|
||||
|
||||
\end_inset
|
||||
|
||||
|
||||
\end_layout
|
||||
|
||||
\end_body
|
||||
\end_document
|
@ -11,20 +11,38 @@ typedef struct Buffer {
|
||||
} Buffer;
|
||||
|
||||
Buffer
|
||||
getBuffer(int size)
|
||||
getBuffer(int size) // retrieve new buffer with malloc-ed memory space
|
||||
{
|
||||
float* memSpace = (float*) malloc(size * sizeof(float));
|
||||
Buffer buffer = {memSpace, size, };
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
freeBuffer(Buffer buffer) // little abstraction function to act as buffer destructor
|
||||
{
|
||||
free(buffer.items);
|
||||
}
|
||||
|
||||
void
|
||||
swapBufferMemory(Buffer *first, Buffer *second) // swap memspaces between buffers
|
||||
{
|
||||
float* firstItems = first->items; // swap input buffer and output buffer item pointers
|
||||
first->items = second->items;
|
||||
second->items = firstItems;
|
||||
|
||||
int firstLength = first->length; // swap lengths to iterate correctly
|
||||
first->length = second->length;
|
||||
second->length = firstLength;
|
||||
}
|
||||
|
||||
void // perform aggregation into groupSize (4 in the spec)
|
||||
aggregateBuffer(Buffer bufferIn, Buffer bufferOut, int groupSize)
|
||||
{
|
||||
int requiredGroups = ceil((float)bufferIn.length/groupSize); // number of groups
|
||||
int finalGroupSize = (bufferIn.length % groupSize) * groupSize;
|
||||
int finalGroupSize = (bufferIn.length % groupSize) * groupSize; // work out length of final group if bufferIn not of length that divides nicely
|
||||
|
||||
if(requiredGroups > bufferOut.length) // error
|
||||
if(requiredGroups > bufferOut.length) // error check
|
||||
{
|
||||
putFloat((float)bufferIn.length/groupSize);
|
||||
printf(" length out buffer required, %i provided\n", bufferOut.length);
|
||||
@ -39,9 +57,9 @@ aggregateBuffer(Buffer bufferIn, Buffer bufferOut, int groupSize)
|
||||
int length = groupSize; // length of this group's size
|
||||
if(g == requiredGroups - 1 && finalGroupSize != 0) length = finalGroupSize; // shorten if necessary
|
||||
|
||||
*outputPtr = calculateMean(inputPtr, length); // SET
|
||||
*outputPtr = calculateMean(inputPtr, length); // SET OUTPUT VALUE
|
||||
|
||||
inputPtr += length; // increment both
|
||||
inputPtr += length; // increment both cursors
|
||||
outputPtr++;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -64,8 +64,8 @@
|
||||
<width>321</width>
|
||||
<z>0</z>
|
||||
<height>160</height>
|
||||
<location_x>48</location_x>
|
||||
<location_y>710</location_y>
|
||||
<location_x>51</location_x>
|
||||
<location_y>875</location_y>
|
||||
</plugin>
|
||||
<plugin>
|
||||
se.sics.cooja.plugins.Visualizer
|
||||
@ -74,7 +74,7 @@
|
||||
<viewport>0.9090909090909091 0.0 0.0 0.9090909090909091 153.72871631902638 172.41033658502872</viewport>
|
||||
</plugin_config>
|
||||
<width>314</width>
|
||||
<z>5</z>
|
||||
<z>6</z>
|
||||
<height>179</height>
|
||||
<location_x>40</location_x>
|
||||
<location_y>20</location_y>
|
||||
@ -90,7 +90,7 @@
|
||||
<zoomfactor>500.0</zoomfactor>
|
||||
</plugin_config>
|
||||
<width>1217</width>
|
||||
<z>4</z>
|
||||
<z>5</z>
|
||||
<height>183</height>
|
||||
<location_x>424</location_x>
|
||||
<location_y>852</location_y>
|
||||
@ -102,7 +102,7 @@
|
||||
<decorations>true</decorations>
|
||||
</plugin_config>
|
||||
<width>6</width>
|
||||
<z>6</z>
|
||||
<z>7</z>
|
||||
<height>160</height>
|
||||
<location_x>680</location_x>
|
||||
<location_y>0</location_y>
|
||||
@ -127,11 +127,11 @@
|
||||
<interface>Temperature and Light</interface>
|
||||
<scrollpos>0,0</scrollpos>
|
||||
</plugin_config>
|
||||
<width>397</width>
|
||||
<width>340</width>
|
||||
<z>1</z>
|
||||
<height>179</height>
|
||||
<location_x>2</location_x>
|
||||
<location_y>247</location_y>
|
||||
<location_x>28</location_x>
|
||||
<location_y>227</location_y>
|
||||
</plugin>
|
||||
<plugin>
|
||||
se.sics.cooja.plugins.MoteInterfaceViewer
|
||||
@ -141,10 +141,23 @@
|
||||
<scrollpos>0,0</scrollpos>
|
||||
</plugin_config>
|
||||
<width>317</width>
|
||||
<z>2</z>
|
||||
<z>4</z>
|
||||
<height>206</height>
|
||||
<location_x>28</location_x>
|
||||
<location_y>445</location_y>
|
||||
</plugin>
|
||||
<plugin>
|
||||
se.sics.cooja.plugins.MoteInterfaceViewer
|
||||
<mote_arg>0</mote_arg>
|
||||
<plugin_config>
|
||||
<interface>Button</interface>
|
||||
<scrollpos>0,0</scrollpos>
|
||||
</plugin_config>
|
||||
<width>296</width>
|
||||
<z>2</z>
|
||||
<height>115</height>
|
||||
<location_x>54</location_x>
|
||||
<location_y>699</location_y>
|
||||
</plugin>
|
||||
</simconf>
|
||||
|
||||
|
@ -1,12 +1,17 @@
|
||||
#define READING_INTERVAL 3 //in Hz
|
||||
#define BUFFER_SIZE 9 // length of buffer to populate
|
||||
#define READING_INTERVAL 2 //in Hz
|
||||
#define BUFFER_SIZE 12 // length of buffer to populate
|
||||
|
||||
#define SD_THRESHOLD 300 // whether to aggregate or flatten
|
||||
#define AGGREGATION_GROUP_SIZE 3 // group size to aggregate (4 in spec)
|
||||
#define SD_THRESHOLD_SOME 400 // some activity, compress above, flatten below
|
||||
#define SD_THRESHOLD_LOTS 1000 // lots of activity, don't aggregate
|
||||
|
||||
#define AGGREGATION_GROUP_SIZE 4 // group size to aggregate (4 in spec)
|
||||
|
||||
#define INITIAL_STATE true // whether begins running or not
|
||||
|
||||
#include "contiki.h"
|
||||
|
||||
#include <stdio.h> /* For printf() */
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "io.h"
|
||||
#include "util.h" // for print methods
|
||||
@ -16,50 +21,72 @@
|
||||
static process_event_t event_buffer_full;
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
PROCESS(sensing_process, "Sensing process");
|
||||
PROCESS(aggregator_process, "Aggregator process");
|
||||
PROCESS(sensing_process, "Sensing process"); // collect data
|
||||
PROCESS(aggregator_process, "Aggregator process"); // receive full data buffers for processing
|
||||
|
||||
AUTOSTART_PROCESSES(&sensing_process, &aggregator_process);
|
||||
/*---------------------------------------------------------------------------*/
|
||||
PROCESS_THREAD(sensing_process, ev, data)
|
||||
{
|
||||
/*INIT*/
|
||||
static struct etimer timer;
|
||||
PROCESS_BEGIN();
|
||||
event_buffer_full = process_alloc_event();
|
||||
|
||||
static struct etimer timer;
|
||||
etimer_set(&timer, CLOCK_SECOND/READING_INTERVAL);
|
||||
|
||||
SENSORS_ACTIVATE(light_sensor);
|
||||
leds_off(LEDS_ALL);
|
||||
event_buffer_full = process_alloc_event();
|
||||
|
||||
initIO();
|
||||
|
||||
static bool isRunning = INITIAL_STATE;
|
||||
|
||||
static Buffer buffer;
|
||||
buffer = getBuffer(BUFFER_SIZE);
|
||||
clearBuffer(buffer);
|
||||
printBuffer(buffer);putchar('\n');putchar('\n');
|
||||
/*END INIT*/
|
||||
|
||||
static int counter = 0;
|
||||
while(1)
|
||||
{
|
||||
PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER);
|
||||
leds_off(LEDS_RED);
|
||||
PROCESS_WAIT_EVENT();
|
||||
|
||||
float light_lx = getLight(); // GET
|
||||
if (ev == PROCESS_EVENT_TIMER){
|
||||
if (isRunning == true) {
|
||||
leds_off(LEDS_RED);
|
||||
|
||||
float light_lx = getLight(); // GET
|
||||
|
||||
buffer.items[counter] = light_lx; // STORE
|
||||
|
||||
printf("%2i/%i: ", counter + 1, buffer.length);putFloat(light_lx);putchar('\n'); // DISPLAY VALUE
|
||||
//printBuffer(buffer, BUFFER_SIZE);putchar('\n'); // DISPLAY CURRENT BUFFER
|
||||
|
||||
counter++;
|
||||
if(counter == buffer.length) // CHECK WHETHER FULL
|
||||
{
|
||||
process_post(&aggregator_process, event_buffer_full, &buffer);
|
||||
counter = 0;
|
||||
buffer = getBuffer(BUFFER_SIZE);
|
||||
buffer.items[counter] = light_lx; // STORE
|
||||
|
||||
printf("%2i/%i: ", counter + 1, buffer.length);putFloat(light_lx);putchar('\n'); // DISPLAY CURRENT VALUE
|
||||
//printBuffer(buffer);putchar('\n'); // DISPLAY CURRENT BUFFER
|
||||
|
||||
counter++;
|
||||
if(counter == buffer.length) // CHECK WHETHER FULL
|
||||
{
|
||||
process_post(&aggregator_process, event_buffer_full, &buffer); // pass buffer to processing thread
|
||||
counter = 0;
|
||||
buffer = getBuffer(BUFFER_SIZE); // get new buffer for next data, no freeing in this thread
|
||||
}
|
||||
}
|
||||
|
||||
etimer_reset(&timer);
|
||||
}
|
||||
/* BUTTON CLICKED */
|
||||
else if (ev == sensors_event && data == &button_sensor)
|
||||
{
|
||||
isRunning = !isRunning;
|
||||
if (isRunning == true)
|
||||
{
|
||||
printf("Starting...\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Stopping, clearing buffer...\n");
|
||||
//freeBuffer(buffer);
|
||||
//buffer = getBuffer(BUFFER_SIZE);
|
||||
counter = 0; // just reset counter, used as index on buffer items, will overwrite
|
||||
}
|
||||
}
|
||||
|
||||
etimer_reset(&timer);
|
||||
}
|
||||
|
||||
PROCESS_END();
|
||||
@ -76,8 +103,8 @@ PROCESS_THREAD(aggregator_process, ev, data)
|
||||
|
||||
Buffer fullBuffer = *(Buffer *)data;
|
||||
/*********************/
|
||||
handleBufferRotation(fullBuffer);
|
||||
free(fullBuffer.items);
|
||||
handleBufferRotation(&fullBuffer); // pass by reference, edited if lots of activity
|
||||
freeBuffer(fullBuffer);
|
||||
/*********************/
|
||||
}
|
||||
|
||||
@ -86,26 +113,41 @@ PROCESS_THREAD(aggregator_process, ev, data)
|
||||
/*---------------------------------------------------------------------------*/
|
||||
// Buffer filled with readings, process and aggregate
|
||||
void
|
||||
handleBufferRotation(Buffer inBuffer)
|
||||
handleBufferRotation(Buffer *inBufferPtr)
|
||||
{
|
||||
printf("Buffer full, aggregating\n\n");
|
||||
|
||||
Buffer inBuffer = *inBufferPtr;
|
||||
Buffer outBuffer; // OUTPUT BUFFER HOLDER
|
||||
// above pointer is assigned a buffer in either of the below cases
|
||||
|
||||
Stats sd = calculateStdDev(inBuffer.items, inBuffer.length); // GET BUFFER STATISTICS
|
||||
if(sd.std > SD_THRESHOLD)
|
||||
{// buffer length by 4
|
||||
printf("Significant STD: ");putFloat(sd.std);printf(", compressing buffer\n");
|
||||
|
||||
/* LOTS OF ACTIVITY - LEAVE */
|
||||
if(sd.std > SD_THRESHOLD_LOTS)
|
||||
{
|
||||
printf("Lots of activity, std. dev.: ");putFloat(sd.std);printf(", leaving as-is\n");
|
||||
|
||||
outBuffer = getBuffer(1); // get a dummy buffer, will swap items for efficiency
|
||||
|
||||
swapBufferMemory(inBufferPtr, &outBuffer); // ensures both are freed but no need to copy items
|
||||
|
||||
}
|
||||
/* SOME ACTIVITY - AGGREGATE */
|
||||
else if(sd.std > SD_THRESHOLD_SOME)
|
||||
{
|
||||
printf("Some activity, std. dev.: ");putFloat(sd.std);printf(", compressing buffer\n");
|
||||
|
||||
int outLength = ceil((float)inBuffer.length/AGGREGATION_GROUP_SIZE); // CALCULATE NUMBER OF OUTPUT ELEMENTS
|
||||
outBuffer = getBuffer(outLength); // CREATE OUTPUT BUFFER
|
||||
|
||||
aggregateBuffer(inBuffer, outBuffer, AGGREGATION_GROUP_SIZE);
|
||||
|
||||
}else
|
||||
{// buffer length to 1
|
||||
printf("Insignificant STD: ");putFloat(sd.std);printf(", squashing buffer\n");
|
||||
}
|
||||
/* NO ACTIVITY - FLATTEN */
|
||||
else
|
||||
{
|
||||
printf("Insignificant std. dev.: ");putFloat(sd.std);printf(", squashing buffer\n");
|
||||
|
||||
outBuffer = getBuffer(1); // CREATE OUTPUT BUFFER
|
||||
outBuffer.items[0] = sd.mean;
|
||||
@ -114,7 +156,7 @@ handleBufferRotation(Buffer inBuffer)
|
||||
|
||||
/*********************/
|
||||
handleFinalBuffer(outBuffer); // PASS FINAL BUFFER
|
||||
free(outBuffer.items); // RELEASE ITEMS
|
||||
freeBuffer(outBuffer); // RELEASE ITEMS
|
||||
/*********************/
|
||||
}
|
||||
|
||||
|
Binary file not shown.
@ -2,8 +2,17 @@
|
||||
#define _IO_GUARD
|
||||
|
||||
#include "dev/light-sensor.h"
|
||||
#include "dev/button-sensor.h"
|
||||
#include "dev/leds.h"
|
||||
|
||||
void
|
||||
initIO()
|
||||
{
|
||||
SENSORS_ACTIVATE(light_sensor);
|
||||
SENSORS_ACTIVATE(button_sensor);
|
||||
leds_off(LEDS_ALL);
|
||||
}
|
||||
|
||||
// get float from light sensor including transfer function
|
||||
float
|
||||
getLight(void)
|
||||
|
@ -36,7 +36,8 @@ calculateMean(float buffer[], int length)
|
||||
printf("%i items is not valid length\n", length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* SUM */
|
||||
float sum = 0;
|
||||
int i;
|
||||
for(i = 0; i < length; i++)
|
||||
@ -44,7 +45,7 @@ calculateMean(float buffer[], int length)
|
||||
sum += buffer[i];
|
||||
}
|
||||
|
||||
return sum / length;
|
||||
return sum / length; // DIVIDE ON RETURN
|
||||
}
|
||||
|
||||
Stats
|
||||
@ -63,8 +64,8 @@ calculateStdDev(float buffer[], int length)
|
||||
int i;
|
||||
for(i = 0; i < length; i++)
|
||||
{
|
||||
float diffFromMean = buffer[i] - stats.mean;
|
||||
sum += diffFromMean*diffFromMean;
|
||||
float diffFromMean = buffer[i] - stats.mean; // (xi - mu)
|
||||
sum += diffFromMean*diffFromMean; // Sum(diff squared)
|
||||
}
|
||||
|
||||
stats.std = sqrt(sum/length);
|
||||
|
Loading…
Reference in New Issue
Block a user