BlueIno

 

blue.ino 

 

// Sample application that communicates with Heximus Android app
// Author. C. Hannukainen 2019,2020,2021,2022

// this sketch uses a BMP180, a RTC and an analog to digital converter.
// We are measuring voltage,temperature, and humidity.
// The RTC returns the number of seconds since 1970 which is written to each record.

// define the schema as such, use the exact spelling

// FLOAT = 4 bytes
// DOUBLE = 8 bytes // only 8 bytes on DUO
// INTEGER = 2 bytes
// LONG = 4 bytes

// CHANGE these two lines to create your database schema that matchs your device and sensors.
#define SCHEMASIZE 8
String schema[SCHEMASIZE] = {"SECS1970","LONG", "Voltage","FLOAT","Temperature","FLOAT","Pressure","FLOAT"};

// this structure holds what you are interested in storing and must match the schema line above in the EXACT SAME order
struct Pack // User modify this Pack Structure to suit your data
{
unsigned long secs1970; // 4
float voltage; // 4
float temperature; // 4
float pressure; // 4
};


unsigned long interval = 1000ul * 10ul; // polling interval (milliseconds) USE ul after the number !!! Otherwise it will be 16 bit and overflow
// initially set low to 10 seconds for initial testing
long utcoffset = 0;

// circular buffer code
//#define CBSIZE 128 // number of records before buffer wraps // uses 112%, 2296 bytes, using this size will crash the nano, but would work on a MEGA since it has more RAM.
#define CBSIZE 72 // number of records before buffer wraps // uses 65%, 1336 bytes NO warnings // Multiply CBSIZE by Pack Structure Size to get idea of memory footprint
// using CBSIZE 72 with an interval of 1 hour , we can store 3 days worth of data.



// Circular buffer object DO NOT MODIFY this structure until you fully understand what it is doing and how it works
typedef struct
{
int size; /* maximum number of elements */
int start; /* index of oldest element */
int end; /* index at which to write new element */
struct Pack pack[CBSIZE+1];
} CircularBuffer;


// incoming packets will look like this <<<<<<<< ----------------- INCOMING PACKET
// magic 2 bytes
// len 2
// numrecords 2
// data n bytes
// crc 2 crc calculate from magic till end of data, not including crc


// The trick to using a circular buffer and not losing data is to extract the data before the circular buffer wraps.
// This is a function of interval time and the size of the buffer.
// Example. if your buffer size CBSIZE is 100, and your interval is 1 hour, interval = 1000 * 3600, you can go 100 hours without losing data.
// So if you come by with your cell phone before 100 hours has passed since your last visit, you will not lose records.
// Unfortunately you cannot set CBSIZE to a very large value since there is extremely limited memory on the Arduino. See above CBSIZE comments
// The size of your Pack structure and CBSIZE dictates how much memory is required
// Unfortunately, there is no method to get the upper limit nor get a usefull error message. The Arduino just goes off into the weeds and locks up.
// So start off with small values to get it working, and then with experimetation you can increase the buffer size.
// When you compile in Arduino IDE, you will get messages if you are getting low on memory. Try to stay out of that zone were you are getting warning messages.
// And then back off a quarter turn. (:


#include <Wire.h>
// include the liquid library code:
#include <LiquidCrystal_I2C.h>
#include "DHT.h"
#include <EEPROM.h>
#include "RTClib.h"
#include <SFE_BMP180.h> // needed for bmp180 pressure
#include <Adafruit_ADS1015.h> // // using the analog to digital converter


unsigned long previousMillis = 0;
unsigned long ivalue32 = 0;

// The lcd is optional but is highly usefull for debugging your sketch. I use the 4 line version that uses SDL/SDA
// using SDL/SDA devices simplifies device hook up, that is, less wires.
// the liquid crystal can be a little tricky setting up. We are using the 4 line version in this sketch.
//LiquidCrystal_I2C lcd(0x3f, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set the LCD I2C address, if it's not working try 0x27
//LiquidCrystal_I2C lcd(0x3f, 16,4); // try 0x27
LiquidCrystal_I2C lcd(0x3f,16,4); // try 0x27
//LiquidCrystal_I2C lcd(0x27, 16,2); // try 0x27



SFE_BMP180 pressure; // using a BMP180 pressure and temperature sensor
float H1 = 0;
float TH = 0;
float P1 = 0;
float T1 = -273.0;
int P1old = 0;


// This is also a optional device. So far it has been very reliable
// we are using an analog to digital converter in this sketch
//ADS1115 adc0(ADS1115_DEFAULT_ADDRESS); // 48, tie address pin to gnd
Adafruit_ADS1115 ads; /* Use this for the 16-bit version */


char data = 0;
byte jack = 0;
int i = 0;
int j = 0;
int s = 0;
int sentbytes = 0;
char abc[64];
int adsize = 0;
int presize = 0;
int savecbstart = 0;
unsigned char btout[64];
int magic = 0;
int len = 0;
int numrecs = 0;
int func,pin,ivalue16;
#define SLAVE_ADDRESS 0x04
byte ACK = 0x06;
byte NAK = 0x15;
int serialmode = 0; // when set to 1, sending raw bytes, no packets, no interpretation , usefull for testing and seting up bluetooth devices
unsigned char serialbyte;
unsigned long secs1970 = 0;
int icc = 0;
int hasrtc; // 0 no rtc, 1 has rtc


int year = 2021;
int month = 10;
int day = 29;
int hour = 0;
int minute = 0;
int second = 0;


union jack
{
byte b[4];
unsigned long l;
};

typedef struct
{
int magic; /* magic Hex1 2 bytes watch out, int is 4 bytes on Android */
int len; /* length of data 2 bytes watch out int is 4 bytes on Android */
int numrecords; /* number of records 2 bytes Total is 8 bytes for Preamble structure */
} Preamble;

// followed by len bytes of data
// followed by crc

Preamble pream;

union jack jacker;

DateTime now;
RTC_DS1307 rtc;



// Checking and using CRC "Cyclic Redundancy Check" may seem overkill, but it can happen, interference, out of range, this all happens.
// We are using 16 bit CRC, 16 bit is still efficent and will catch most errors.
// The Heximus application uses this same CRC code. Changing anything on this side pertaining to CRC will break the protocol with
// Heximus and this application will not communicate properly.
// We calcualate CRC as a running total to save memory. That is why CRC is always the last field in the structure

uint16_t crc16 = 0xFFFF;
uint16_t crcpack = 0;

//uint16_t crc16 (const uint8_t * buffer, uint32_t size)
void CRC16 (byte *buffer, int size)
{
// uint16_t crc16 = 0xFFFF; // we init outside since we want a running total

if (buffer && size)
while (size--)
{
crc16 = (crc16 >> 8) | (crc16 << 8);
crc16 ^= *buffer++;
crc16 ^= ((unsigned char) crc16) >> 4;
crc16 ^= crc16 << 12;
crc16 ^= (crc16 & 0xFF) << 5;
}

// return crc16;
}

// Start circular buffer code vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
CircularBuffer cb;
struct Pack adelem;

void cbInit(int size)
{
cb.size = size + 1; // include empty elem
cb.start = 0;
cb.end = 0;
}

int cbIsFull()
{
return (cb.end + 1) % cb.size == cb.start;
}

int cbSize()
{
int size = (cb.size - cb.start + cb.end) % cb.size;
return size;
}

int cbIsEmpty()
{
return cb.end == cb.start;
}

// Write an element, overwriting oldest element if buffer is full. App can choose to avoid the overwrite by checking cbIsFull().
void cbWrite(struct Pack *pack)
{
cb.pack[cb.end] = *pack;
cb.end = (cb.end + 1) % cb.size;
if (cb.end == cb.start)
cb.start = (cb.start + 1) % cb.size; // full, overwrite
}

// Read oldest element. App must ensure !cbIsEmpty() first.
void cbRead(struct Pack *pack)
{
*pack = cb.pack[cb.start];
cb.start = (cb.start + 1) % cb.size;
}

// End circular buffer code ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^



// this routine simply prints the time to the lcd display, can be removed.
void showtime()
{
if (hasrtc == 0)
return;

now = rtc.now(); // Grab the time from the RTC

lcd.setCursor(0, 2);

lcd.print(now.year(), DEC);
lcd.print('/');
lcd.print(now.month(), DEC);
lcd.print('/');
lcd.print(now.day(), DEC);

lcd.setCursor(0, 3);

lcd.print(now.hour(), DEC);
lcd.print(':');
lcd.print(now.minute(), DEC);
lcd.print(':');
lcd.print(now.second(), DEC);
lcd.print('__');
}

// A0 - A15 can be different on different boards
int pin2analog(int pin)
{
int v = -1;
switch (pin)
{
case 0: v = A0; break;
case 1: v = A1; break;
case 2: v = A2; break;
case 3: v = A3; break;
case 4: v = A4; break;
case 5: v = A5; break;
case 6: v = A6; break;
case 7: v = A7; break;
case 8: v = A8; break;
case 9: v = A9; break;
case 10: v = A10; break;
case 11: v = A11; break;
case 12: v = A12; break;
case 13: v = A13; break;
case 14: v = A14; break;
case 15: v = A15; break;
}
return v;
}


// bmp180 does not have humidity, need another sensor for that
void getpressureprim()
{
char status;
double T,P,p0,a;
float tm;

P1old = P1 + 0.5;

// If you want to measure altitude, and not pressure, you will instead need
// to provide a known baseline pressure.

// Start a temperature measurement:
// If request is successful, the number of ms to wait is returned.
// If request is unsuccessful, 0 is returned.

status = pressure.startTemperature();
if (status != 0)
{
// Wait for the measurement to complete:
delay(status);

// Retrieve the completed temperature measurement:
// Note that the measurement is stored in the variable T.
// Function returns 1 if successful, 0 if failure.

status = pressure.getTemperature(T);
if (status != 0)
{
// Print out the measurement:
// Start a pressure measurement:
// The parameter is the oversampling setting, from 0 to 3 (highest res, longest wait).
// If request is successful, the number of ms to wait is returned.
// If request is unsuccessful, 0 is returned.

status = pressure.startPressure(3);
if (status != 0)
{
// Wait for the measurement to complete:
delay(status);

// Retrieve the completed pressure measurement:
// Note that the measurement is stored in the variable P.
// Note also that the function requires the previous temperature measurement (T).
// (If temperature is stable, you can do one temperature measurement for a number of pressure measurements.)
// Function returns 1 if successful, 0 if failure.

status = pressure.getPressure(P,T);
if (status != 0)
{
// Print out the measurement:

P1 = P;
T1 = T;

// The pressure sensor returns abolute pressure, which varies with altitude.
// To remove the effects of altitude, use the sealevel function and your current altitude.
// This number is commonly used in weather reports.
// Parameters: P = absolute pressure in mb, ALTITUDE = current altitude in m.
// Result: p0 = sea-level compensated pressure in mb

}
else Serial.println("error retrieving pressure measurement\n");
}
else Serial.println("error starting pressure measurement\n");
}
else Serial.println("error retrieving temperature measurement\n");
}
else Serial.println("error starting temperature measurement\n");

}



void setup()
{
// put your setup code here, to run once:

Serial.begin(9600);
//Serial.begin(38400);

Wire.begin(SLAVE_ADDRESS); // NEED TO DO THIS STEP FIRST !!
rtc.begin(); // start up the RTC


ads.setGain(GAIN_TWOTHIRDS); // 2/3x gain +/- 6.144V 1 bit = 3mV 0.1875mV (default) 6.144 volt
ads.begin();

lcd.init();
lcd.backlight();
lcd.begin(16,4);

lcd.setCursor(0,1);

if (! rtc.isrunning())
{
lcd.print("NO RTC");
hasrtc = 0;
}
else
{
hasrtc = 1;
}

// read the interval from eeprom
jacker.b[0] = EEPROM.read(0);
jacker.b[1] = EEPROM.read(1);
jacker.b[2] = EEPROM.read(2);
jacker.b[3] = EEPROM.read(3);
interval = jacker.l;
if (interval <= 0 || interval > 86400000) // sanity check
interval = 10000; // 10 seconds is the default


// the rtc chip does not support timezones
// so we read the offset from eprom
// that is set by user with bt.setutcoffset
jacker.b[0] = EEPROM.read(4);
jacker.b[1] = EEPROM.read(5);
jacker.b[2] = EEPROM.read(6);
jacker.b[3] = EEPROM.read(7);
utcoffset = jacker.l;
if (utcoffset <= 86400 || utcoffset >= 86400) // sanity check
utcoffset = 0; // 0 seconds is the default



lcd.setCursor(0, 2);


if (pressure.begin())
{
Serial.println("BMP180 init success");
}
else
{
Serial.println("BMP180 init fail\n\n");
}


cbInit(CBSIZE); // init the circular buffer

adsize = sizeof(adelem);
presize = sizeof(pream);
sprintf(abc, "%d presize %d", adsize, presize);
lcd.setCursor(0,0);
lcd.print(abc);


}

// Software reset function for arudino , basically reboot at memory address 0
void (* resetFunc) (void) = 0;


void sendpacket(int magicin,int sendsize,unsigned char *bout)
{
sentbytes = 0;
unsigned char pre[6];

pream.magic = magicin;
pream.len = sendsize + sizeof(crc16);
pream.numrecords = 0; // not used
memcpy(pre,&pream,presize); // presize , the size of the preamble 6 bytes for this design

crc16 = 0xFFFF;
CRC16(pre, 6);

Serial.write(pre,presize); //send first 6 bytes

sentbytes += presize;

CRC16(btout, sendsize);
Serial.write(bout,sendsize); // sending the data
sentbytes += sendsize;

Serial.write((byte *) &crc16,2); // send computed CRC at end of data
sentbytes += 2;

lcd.setCursor(0,3);
sprintf(abc, "snt %d ", sentbytes);
lcd.print(abc);

}



void ReadSensors()
{
if (hasrtc == 0)
{
secs1970 = millis(); // since no RTC present, use millis()
}
else
{
now = rtc.now(); // Grab the time from the RTC
secs1970 = now.unixtime(); // convert it to seconds since 1970
secs1970 = secs1970 - utcoffset;
}

int16_t v = ads.readADC_SingleEnded(0);
float voltage = (float) v * 0.1875 / 1000.0;

getpressureprim();


// Fill the structure with your values and write to circular buffer
adelem.secs1970 = secs1970;
adelem.voltage = voltage;
adelem.pressure = P1;
adelem.temperature = T1;
}


void loop()
{
jack = Serial.available();

if (serialmode == 1)
{
if (jack >= 1)
{
serialbyte = Serial.read(); //Read the incoming data and store it into variable data
lcd.clear();
lcd.setCursor(0,0);
sprintf(abc, "in %d %x",serialbyte,serialbyte);
lcd.print(abc);
if (serialbyte == 1)
{
Serial.write((byte *) &ACK,1); // Send ACK ascii 6
}
if (serialbyte == 2)
{
serialmode = 0;
}

if (serialbyte == 3)
{
Serial.write((byte *) abc,8);
}

}
return;
}

// this serial code needs to be on main loop since packets can come in at any time and must be read to capture without loss
if (jack > 0) // Send data only when you receive data:
{
btout[icc] = Serial.read(); //Read the incoming data and store it into variable data
icc = icc + 1;

if (icc >= 63)
{
icc = 0;
len = 0;
// problem here, maybe old data, get and trash
while (Serial.available() > 0)
serialbyte = Serial.read();
}

if (icc == 6) // at 6 bytes we have the preamble
{
magic = (btout[0] << 8) & 0xff00 | (btout[1] << 0) & 0x00ff;
len = (btout[2] << 8) & 0xff00 | (btout[3] << 0) & 0x00ff;
numrecs = (btout[4] << 8) & 0xff00 | (btout[5] << 0) & 0x00ff;

lcd.clear();
lcd.setCursor(0,0);
sprintf(abc, "ma %x %d %d", magic, len, numrecs);
lcd.print(abc);

if (len < 0 || len >= 63)
{
// problem here, maybe old data, get and trash
while (Serial.available() > 0)
serialbyte = Serial.read();
icc = 0;
len = 0;
}

}


if (icc == (6 + len) ) // now that we have entire packet,check the CRC
{
crcpack = (btout[4 + len] << 8) & 0xff00 | (btout[5 + len] << 0) & 0x00ff;
crc16 = 0xFFFF;
CRC16(btout, 6 + len - 2);

sprintf(abc, "crc %d %d", crcpack, crc16);
lcd.setCursor(0,2);
lcd.print(abc);

if (crcpack != crc16) // crc mismatch
{
lcd.setCursor(0,3);
lcd.print("Bad CRC");
icc = 0;
len = 0;
magic = 0;
// Serial.write((byte *) &NAK,1); // crc error send NAK hex 15 dec 21
}

if (magic == 0x4242)
resetFunc();



switch (magic)
{

case 0x4856: // create a single packet by reading sensors
icc = 0;
len = 0;
ReadSensors(); // get latest data readings into adelem structure
memcpy(btout,&adelem,adsize); // copy our sensor data to btout array by memcopy the structure
sendpacket(0x4857,adsize,btout); // add one to magic send back via bluetooth to the cell phone the data
break;


case 0x4880: // called by bt.interval = integer value in milliseconds
s = 6;
icc = 0;
len = 0;

// endian mismatch fix
jacker.b[0] = btout[s+3];
jacker.b[1] = btout[s+2];
jacker.b[2] = btout[s+1];
jacker.b[3] = btout[s+0];

interval = jacker.l;
ivalue32 = jacker.l;


btout[0] = jacker.b[0];
btout[1] = jacker.b[1];
btout[2] = jacker.b[2];
btout[3] = jacker.b[3];


EEPROM.update(0,btout[0]); // using the update method,
EEPROM.update(1,btout[1]); // only writes to eeprom if data is different
EEPROM.update(2,btout[2]); // eeprom only lasts 100,000 write cycles
EEPROM.update(3,btout[3]);

jacker.b[0] = EEPROM.read(0);
jacker.b[1] = EEPROM.read(1);
jacker.b[2] = EEPROM.read(2);
jacker.b[3] = EEPROM.read(3);
interval = jacker.l;

sprintf(abc, "eeprom %ld",interval);
lcd.setCursor(0,2);
lcd.print(abc);
lcd.setCursor(0,3);
lcd.print(abc);

memcpy(btout,&ivalue32,4); // copy interval to output buffer
sendpacket(magic+1,4,btout); // send back the old value

break;



case 0x4882: // called by bt.utcoffset = integer value in seconds
s = 6;
icc = 0;
len = 0;

// endian mismatch fix
jacker.b[0] = btout[s+3];
jacker.b[1] = btout[s+2];
jacker.b[2] = btout[s+1];
jacker.b[3] = btout[s+0];

utcoffset = jacker.l;
ivalue32 = jacker.l;


btout[0] = jacker.b[0];
btout[1] = jacker.b[1];
btout[2] = jacker.b[2];
btout[3] = jacker.b[3];


EEPROM.update(4,btout[0]); // using the update method,
EEPROM.update(5,btout[1]); // only writes to eeprom if data is different
EEPROM.update(6,btout[2]); // eeprom only lasts 100,000 write cycles
EEPROM.update(7,btout[3]);

jacker.b[0] = EEPROM.read(4);
jacker.b[1] = EEPROM.read(5);
jacker.b[2] = EEPROM.read(6);
jacker.b[3] = EEPROM.read(7);
utcoffset = jacker.l;

sprintf(abc, "Eoff %ld",utcoffset);
lcd.setCursor(0,2);
lcd.print(abc);
lcd.setCursor(0,3);
lcd.print(abc);

memcpy(btout,&ivalue32,4); // copy interval to output buffer
sendpacket(magic+1,4,btout); // send back the old value

break;









case 0x4850: // get interval
case 0x4852: // get record count
case 0x4854: // get max record count
case 0x4870: // rtc secs 1970
case 0x4872: // millis
case 0x4874: // utc offset

icc = 0;
len = 0;


if (magic == 0x4850) // get interval
{
ivalue32 = interval;
}

if (magic == 0x4874) // get utc offset
{
ivalue32 = utcoffset;
}

if (magic == 0x4852)
{
ivalue32 = cbSize(); // get number of records currently in circular buffer
}

if (magic == 0x4854) // get the maximum number of records the circular buffer can hold
{
ivalue32 = CBSIZE; // circular buffer size
}

if (magic == 0x4870) // get secs1970 unixtime
{
now = rtc.now(); // Grab the time from the RTC
ivalue32 = now.unixtime(); // convert it to seconds since 1970
ivalue32 = ivalue32 - utcoffset;
}

if (magic == 0x4872) // get milli
{
ivalue32 = millis(); // get millis()
}

sprintf(abc, "mag %x v %ld",magic,ivalue32);
lcd.clear();
lcd.setCursor(0,1);
lcd.print(abc);
lcd.setCursor(0,2);
lcd.print(abc);

// copy long value, 4 bytes
memcpy(btout,&ivalue32,4); // copy interval to output buffer

sendpacket(magic+1,4,btout); // add one to magic


break;


case 0x4840: // get the time from the RTC we connected to the arduino
icc = 0;
len = 0;
func = (btout[6] << 8) & 0xff00 | (btout[7] << 0) & 0x00ff;
if (hasrtc == 1)
{
now = rtc.now(); // Grab the time from the RTC
year = now.year();
month = now.month();
day = now.day();
hour = now.hour();
minute = now.minute();
second = now.second();
}

memcpy(&btout[0],(byte *)&year,2);
memcpy(&btout[2],(byte *)&month,2);
memcpy(&btout[4],(byte *)&day,2);
memcpy(&btout[6],(byte *)&hour,2);
memcpy(&btout[8],(byte *)&minute,2);
memcpy(&btout[10], (byte *)&second,2);

sendpacket(0x4841,12,btout); // 12 = 6 * 2 from above

break;


case 0x4844: // setting time
// have all the time data now

icc = 0;
len = 0;

year = (btout[6] << 8) & 0xff00 | (btout[7] << 0) & 0x00ff;
month = (btout[8] << 8) & 0xff00 | (btout[9] << 0) & 0x00ff;
day = (btout[10] << 8) & 0xff00 | (btout[11] << 0) & 0x00ff;
hour = (btout[12] << 8) & 0xff00 | (btout[13] << 0) & 0x00ff;
minute = (btout[14] << 8) & 0xff00 | (btout[15] << 0) & 0x00ff;
second = (btout[16] << 8) & 0xff00 | (btout[17] << 0) & 0x00ff;

sprintf(abc, "year %d", year);
lcd.setCursor(0,2);
lcd.print(abc);
rtc.adjust(DateTime(year,month,day,hour,minute,second));

ivalue16 = 6;
memcpy(&btout[0],(byte *)&ivalue16,2);

sendpacket(0x4845,2,btout);

break;


default:
sprintf(abc, "??? %d", magic);
lcd.setCursor(0,3);
lcd.print(abc);
break;

}


if (magic == 0x4860) // setting arduino pin
{

icc = 0;
len = 0;

func = (btout[6] << 8) & 0xff00 | (btout[7] << 0) & 0x00ff;
pin = (btout[8] << 8) & 0xff00 | (btout[9] << 0) & 0x00ff;
ivalue16 = (btout[10] << 8) & 0xff00 | (btout[11] << 0) & 0x00ff;

if (func == 1)
{
if (ivalue16 == 0)
pinMode(pin,INPUT);
if (ivalue16 == 1)
pinMode(pin,OUTPUT);
if (ivalue16 == 2)
pinMode(pin,INPUT_PULLUP);
}

if (func == 2)
{
if (ivalue16 == 0)
digitalWrite(pin,LOW);
if (ivalue16 == 1)
digitalWrite(pin,HIGH);
}

if (func == 3)
{
analogWrite(pin,ivalue16);
}

if (func == 4)
{
ivalue16 = digitalRead(pin);
}

if (func == 5)
{
ivalue16 = analogRead(pin);
}

if (func == 6)
{
ivalue16 = pin2analog(pin);
}


sprintf(abc, "f,p,v %d %d %d",func,pin,ivalue16);
lcd.clear();
lcd.setCursor(0,2);
lcd.print(abc);

memcpy(btout,&ivalue16,2); // copy interval to output buffer
sendpacket(magic+1,2,btout); // add one to magic

}



if (magic == 0x4836) // Send the schema to Android so it can create our database for us
{
// get the length of our schema

icc = 0;
len = 0;

int s = 0;
crc16 = 0xFFFF; // we are doing a runing total on crc
// Arduino has limited memory and can't just allocate buffers ad hoc

for (i = 0; i < SCHEMASIZE; ++ i)
{
s += schema[i].length() + 1; // 1 for trailing null
}

pream.magic = 0x4837;
pream.len = s + sizeof(crc16); // size plus crc
pream.numrecords = SCHEMASIZE;

memset(btout,0,sizeof(btout));
memcpy(btout,&pream,presize); // presize , the size of the preamble

CRC16(btout, presize);


Serial.write(btout,presize); // send preamble

for (i = 0; i < SCHEMASIZE; ++ i)
{
memset(btout,0,sizeof(btout));
s = schema[i].length();
schema[i].getBytes(btout,sizeof(btout));

Serial.write(btout,s+1); // writing an extra byte a trailing null
CRC16(btout, s+1); // computing crc into crc16 as we gather each record

}
Serial.write((byte *) &crc16,2); // send CRC at end of data
// we did the crc check on the run without allocating any temporary buffers

}

if (magic == 0x4842) // This code writes out the data of the circular buffer
{
icc = 0;
len = 0;
sentbytes = 0;

short removeflag = (btout[6] << 8) & 0xff00 | (btout[7] << 0) & 0x00ff;


int s = cbSize(); // current number of records in circular buffer
int size = s * adsize; // num recs * structe size

pream.magic = 0x4843;
pream.len = size + sizeof(crc16);
pream.numrecords = cbSize();

memcpy(btout,&pream,presize); // presize , the size of the preamble
Serial.write(btout,presize);


crc16 = 0xFFFF;
CRC16(btout, presize);

sentbytes += presize;


savecbstart = cb.start;

while (!cbIsEmpty())
{
cbRead(&adelem);
memcpy(btout,&adelem,adsize);

CRC16(btout, adsize);
Serial.write(btout,adsize);
sentbytes += adsize;
}

Serial.write((byte *) &crc16,2); // send CRC at end of data
sentbytes += 2;

if (removeflag == 0)
cb.start = savecbstart; // this has effect of reading items but not removing them

// this is needed in situations when we have not recieved acknowledment that the data was recieved by the master ("android device itself")


lcd.setCursor(0,1);

sprintf(abc, "sent %d", sentbytes);

lcd.print(abc);
}

icc = 0;
len = 0;

}
} // end of jack > 1 loop

// Arduino millis() function wraps around in ....
// 2 ^ 32 / 1000 / 3600 / 24 = 49.71 Days

unsigned long currentMillis = millis();

// sprintf(abc, "%ld %ld %ld", currentMillis, previousMillis,interval);
// lcd.clear();
// lcd.setCursor(0,1);
// lcd.print(abc);


if (currentMillis - previousMillis > interval)
{
previousMillis = currentMillis;

ReadSensors();


lcd.setCursor(0,0);
// sprintf(abc, "v=%s ", abc); // no floating point in this land
dtostrf(adelem.voltage, 6, 2, abc); // so use this instead
lcd.print("v = ");
lcd.print(abc);

dtostrf(adelem.pressure, 6, 1, abc);
lcd.print(" p = ");
lcd.print(abc);
lcd.print(" ");


cbWrite(&adelem); // write to circular buffer

lcd.setCursor(0,1);
sprintf(abc, "%ld", millis());
lcd.print(abc);
lcd.print(" ");
sprintf(abc, "%ld", secs1970);
lcd.print(abc);

showtime();

}


}