ecezo: initial implementation; C, C++; FTI + examples

Signed-off-by: Jon Trulson <jtrulson@ics.com>
This commit is contained in:
Jon Trulson
2016-11-18 17:32:34 -07:00
parent d4559878df
commit ad275e1d41
18 changed files with 1857 additions and 0 deletions

666
src/ecezo/ecezo.c Normal file
View File

@ -0,0 +1,666 @@
/*
* Author: Jon Trulson <jtrulson@ics.com>
* Copyright (c) 2016 Intel Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include <upm_utilities.h>
#include "ecezo.h"
// "Typical" command completion delay in ms
#define CMD_DELAY (350)
// uncomment for dubugging
//#define ECEZO_DEBUG (1)
// I2C read helper
static int readBytes(const ecezo_context dev, uint8_t *buffer, int len)
{
assert(dev != NULL);
assert(dev->i2c != NULL);
bool done = false;
int rv;
int retries = 10;
while (!done && (retries-- > 0))
{
if ((rv = mraa_i2c_read(dev->i2c, buffer, len)) < 0)
{
printf("%s: mraa_i2c_read(code) failed.\n", __FUNCTION__);
return rv;
}
#if defined(ECEZO_DEBUG)
printf("CODE: %02x\n", buffer[0]);
#endif
if (buffer[0] == 0xff || buffer[0] == 0x02)
{
// no data available, or error
return -1;
}
else if (buffer[0] == 0x01)
{
// data is ready
done = true;
// now we need to move the data one byte down so the rest
// of this driver can work as-is.
memmove(buffer, (buffer + 1), len - 1);
}
else
{
// buffer[0] 0xfe - data is pending. wait and loop again.
upm_delay_ms(CMD_DELAY);
}
}
if (retries <= 0)
{
printf("%s: timed out waiting for correct response.\n", __FUNCTION__);
return -1;
}
#if defined(ECEZO_DEBUG)
printf("%s: Got %d bytes\n", __FUNCTION__, rv);
for (int i=0; i<rv; i++)
{
printf("%02x (%c) ", buffer[i],
isprint(buffer[i]) ? buffer[i] : '@');
}
printf("\n");
#endif // ECEZO_DEBUG
return rv;
}
static upm_result_t generic_init(const ecezo_context dev)
{
assert(dev != NULL);
// do some generic initialization
bool error = false;
// turn off response (*OK) codes (UART only)
if (dev->uart)
{
if (ecezo_send_command(dev, "Response,0", NULL, 0) < 0)
error = true;
}
// turn off continuous sampling
if (ecezo_set_continuous(dev, false))
error = true;
// make sure all parameters are enabled
if (ecezo_send_command(dev, "O,EC,1", NULL, 0) < 0)
error = true;
if (ecezo_send_command(dev, "O,TDS,1", NULL, 0) < 0)
error = true;
if (ecezo_send_command(dev, "O,S,1", NULL, 0) < 0)
error = true;
if (ecezo_send_command(dev, "O,SG,1", NULL, 0) < 0)
error = true;
if (error)
return UPM_ERROR_OPERATION_FAILED;
else
return UPM_SUCCESS;
}
static upm_result_t decode_report(const ecezo_context dev, char *data)
{
assert(dev != NULL);
char *startptr = data;
char *endptr = NULL;
float val;
// the format of the data string should be: ec,tds,s,sg
// ec
val = strtof(startptr, &endptr);
if (startptr == endptr)
{
// error
return UPM_ERROR_OPERATION_FAILED;
}
dev->ec = val;
startptr = endptr + 1;
// tds
val = strtof(startptr, &endptr);
// error
if (startptr == endptr)
return UPM_ERROR_OPERATION_FAILED;
dev->tds = val;
startptr = endptr + 1;
// salinity
val = strtof(startptr, &endptr);
// error
if (startptr == endptr)
return UPM_ERROR_OPERATION_FAILED;
dev->salinity = val;
startptr = endptr + 1;
// sg
val = strtof(startptr, &endptr);
if (startptr == endptr)
return UPM_ERROR_OPERATION_FAILED;
dev->sg = val;
return UPM_SUCCESS;
}
static bool ecezo_data_available(const ecezo_context dev, unsigned int millis)
{
assert(dev != NULL);
// i2c, we don't support this
if (dev->i2c)
{
return false;
}
// uart
if (mraa_uart_data_available(dev->uart, millis))
return true;
else
return false;
}
// uart init
ecezo_context ecezo_uart_init(unsigned int uart, unsigned int baudrate)
{
ecezo_context dev =
(ecezo_context)malloc(sizeof(struct _ecezo_context));
if (!dev)
return NULL;
// zero out context
memset((void *)dev, 0, sizeof(struct _ecezo_context));
// initialize the MRAA contexts
// uart, default should be 8N1
if (!(dev->uart = mraa_uart_init(uart)))
{
printf("%s: mraa_uart_init() failed.\n", __FUNCTION__);
ecezo_close(dev);
return NULL;
}
if (mraa_uart_set_baudrate(dev->uart, baudrate))
{
printf("%s: mraa_uart_set_baudrate() failed.\n", __FUNCTION__);
ecezo_close(dev);
return NULL;
}
mraa_uart_set_flowcontrol(dev->uart, false, false);
if (generic_init(dev))
{
printf("%s: generic_init() failed.\n", __FUNCTION__);
ecezo_close(dev);
return NULL;
}
return dev;
}
// i2c ublox init
ecezo_context ecezo_i2c_init(unsigned int bus, uint8_t addr)
{
// make sure MRAA is initialized
int mraa_rv;
if ((mraa_rv = mraa_init()) != MRAA_SUCCESS)
{
printf("%s: mraa_init() failed (%d).\n", __FUNCTION__, mraa_rv);
return NULL;
}
ecezo_context dev =
(ecezo_context)malloc(sizeof(struct _ecezo_context));
if (!dev)
return NULL;
// zero out context
memset((void *)dev, 0, sizeof(struct _ecezo_context));
// initialize the MRAA contexts
if (!(dev->i2c = mraa_i2c_init(bus)))
{
printf("%s: mraa_i2c_init() failed.\n", __FUNCTION__);
ecezo_close(dev);
return NULL;
}
if (mraa_i2c_address(dev->i2c, addr))
{
printf("%s: mraa_i2c_address() failed.\n", __FUNCTION__);
ecezo_close(dev);
return NULL;
}
if (generic_init(dev))
{
printf("%s: generic_init() failed.\n", __FUNCTION__);
ecezo_close(dev);
return NULL;
}
return dev;
}
void ecezo_close(ecezo_context dev)
{
assert(dev != NULL);
if (dev->uart)
mraa_uart_stop(dev->uart);
if (dev->i2c)
mraa_i2c_stop(dev->i2c);
free(dev);
}
upm_result_t ecezo_set_continuous(const ecezo_context dev, bool enable)
{
int rv;
if (enable)
rv = ecezo_send_command(dev, "C,1", NULL, 0);
else
rv = ecezo_send_command(dev, "C,0", NULL, 0);
return ((rv < 0) ? UPM_ERROR_OPERATION_FAILED : UPM_SUCCESS);
}
upm_result_t ecezo_set_temperature(const ecezo_context dev, float temp)
{
char buffer[ECEZO_MAX_BUFFER_LEN];
snprintf(buffer, ECEZO_MAX_BUFFER_LEN, "T,%f", temp);
int rv = ecezo_send_command(dev, buffer, NULL, 0);
return ((rv < 0) ? UPM_ERROR_OPERATION_FAILED : UPM_SUCCESS);
}
upm_result_t ecezo_set_led(const ecezo_context dev, bool enable)
{
int rv;
if (enable)
rv = ecezo_send_command(dev, "L,1", NULL, 0);
else
rv = ecezo_send_command(dev, "L,0", NULL, 0);
return ((rv < 0) ? UPM_ERROR_OPERATION_FAILED : UPM_SUCCESS);
}
upm_result_t ecezo_set_k_value(const ecezo_context dev, float k)
{
char buffer[ECEZO_MAX_BUFFER_LEN];
// the K value must be between 0.1 and 10.0
if (k < 0.1 || k > 10.0)
{
printf("%s: K value must be between 0.1 and 10.0\n", __FUNCTION__);
return UPM_ERROR_OUT_OF_RANGE;
}
snprintf(buffer, ECEZO_MAX_BUFFER_LEN, "K,%f", k);
int rv = ecezo_send_command(dev, buffer, NULL, 0);
return ((rv < 0) ? UPM_ERROR_OPERATION_FAILED : UPM_SUCCESS);
}
upm_result_t ecezo_set_sleep(const ecezo_context dev, bool enable)
{
int rv = 0;
if (enable)
rv = ecezo_send_command(dev, "SLEEP", NULL, 0);
else
{
// "WAKE" isn't a real command, but should wake the device up.
// We ignore the return value, as it will likely be an error
// anyway.
ecezo_send_command(dev, "WAKE", NULL, 0);
}
return ((rv < 0) ? UPM_ERROR_OPERATION_FAILED : UPM_SUCCESS);
}
int ecezo_read(const ecezo_context dev, char *buffer, size_t len)
{
assert(dev != NULL);
upm_delay_ms(CMD_DELAY); // delay CMD_DELAY ms to make sure cmd completed
// i2c
if (dev->i2c)
{
return readBytes(dev, (uint8_t *)buffer, len);
}
else
{
// UART
int bytesRead = 0;
while(bytesRead < len)
{
// we read one byte at a time, exiting when either len is
// reached, or a '\r' is found indicating the end of a
// sentence. Most commands (except 'R') require a minimum
// of 300ms to execute, so we wait up to CMD_DELAY ms after all
// data (if any) is read.
if (ecezo_data_available(dev, CMD_DELAY))
{
int br = mraa_uart_read(dev->uart, &buffer[bytesRead], 1);
if (br <= 0)
return br;
if (buffer[bytesRead] == '\r')
{
// if we found a CR, replace it with a 0 byte
buffer[bytesRead++] = 0;
return bytesRead;
}
bytesRead++;
}
else
{
// timed out - ok with responses disabled
return 0;
}
}
}
// anything else is an error
return -1;
}
upm_result_t ecezo_write(const ecezo_context dev, char *buffer, size_t len)
{
assert(dev != NULL);
if (dev->uart)
{
if (mraa_uart_write(dev->uart, buffer, len) != len)
{
printf("%s: mraa_uart_write() failed.\n", __FUNCTION__);
return UPM_ERROR_OPERATION_FAILED;
}
}
else
{
// I2C
if (mraa_i2c_write(dev->i2c, (uint8_t *)buffer, len))
{
printf("%s: mraa_i2c_write() failed.\n", __FUNCTION__);
return UPM_ERROR_OPERATION_FAILED;
}
}
return UPM_SUCCESS;
}
int ecezo_send_command(const ecezo_context dev, char *cmd, char *buffer,
int len)
{
assert(dev != NULL);
if (!cmd)
return -1;
// Our local buffer in case one isn't supplied
char localBuffer[ECEZO_MAX_BUFFER_LEN];
// our read buffer ptr
char *readBuffer = NULL;
if (!buffer || !len)
{
readBuffer = localBuffer;
len = ECEZO_MAX_BUFFER_LEN;
}
else
{
readBuffer = buffer;
}
#if defined(ECEZO_DEBUG)
printf("Command: %s\n", cmd);
#endif // ECEZO_DEBUG
// our write buffer
char writeBuffer[ECEZO_MAX_BUFFER_LEN];
strncpy(writeBuffer, cmd, ECEZO_MAX_BUFFER_LEN);
writeBuffer[ECEZO_MAX_BUFFER_LEN - 1] = 0;
int writelen = strlen(writeBuffer);
if (dev->uart)
{
if (strlen(writeBuffer) >= ECEZO_MAX_BUFFER_LEN - 2)
{
// too big. Should never happen in real life.
printf("%s: cmd writeBuffer too big.\n", __FUNCTION__);
return -1;
}
strcat(writeBuffer, "\r");
}
// for the uart this will now include the added CR, for I2C, this
// will now include the already existing \0 terminator.
writelen++;
// Let the games begin...
int retries = 10;
while (retries-- > 0)
{
if (ecezo_write(dev, writeBuffer, writelen))
{
printf("%s: ecezo_write() failed\n", __FUNCTION__);
return -1;
}
// we wait up to CMD_DELAY ms for an error response, which should be
// more than enough time. No response is also ok, since we
// disable the "*OK" response in the init.
memset((void *)readBuffer, 0, len);
int bytesRead = 0;
if ((bytesRead = ecezo_read(dev, readBuffer, len)) < 0)
{
return -1;
}
// for I2C, we are done at this point
if (dev->i2c)
break;
// for UART, we need some more checks
if (bytesRead && strstr(readBuffer, "*ER"))
{
// need to retry the command
#if defined(ECEZO_DEBUG)
printf("%s: *ER DETECTED, retry\n", __FUNCTION__);
#endif // ECEZO_DEBUG
continue;
}
else if (bytesRead && strchr(readBuffer, '*'))
{
// Some other diagnostic code, output it.
#if defined(ECEZO_DEBUG)
printf("%s: * diagnostic code detected (%s), retry\n",
__FUNCTION__, buffer);
#endif // ECEZO_DEBUG
continue;
}
else
{
// we are done here
#if defined(ECEZO_DEBUG)
printf("%s: bytesRead = %d\n", __FUNCTION__, bytesRead);
#endif // ECEZO_DEBUG
break;
}
}
if (retries <= 0)
{
printf("%s: read timed out and/or and retries exhausted\n",
__FUNCTION__);
return 0;
}
return len;
}
upm_result_t ecezo_update(const ecezo_context dev)
{
assert(dev != NULL);
// first we send a 'R' command to get a reading (takes a minimum
// of 1 second), then we parse out the string values into the
// context variables.
char buffer[ECEZO_MAX_BUFFER_LEN];
// first issue the report command
int rv = ecezo_send_command(dev, "R", buffer, ECEZO_MAX_BUFFER_LEN);
if (rv == 0)
{
printf("%s: timed out waiting for data\n", __FUNCTION__);
return UPM_ERROR_OPERATION_FAILED;
}
else if (rv < 0)
{
printf("%s: error retrieving data\n", __FUNCTION__);
return UPM_ERROR_OPERATION_FAILED;
}
// decode
if (decode_report(dev, buffer))
{
printf("%s: decode_report() failed\n", __FUNCTION__);
return UPM_ERROR_OPERATION_FAILED;
}
return UPM_SUCCESS;
}
float ecezo_get_ec(const ecezo_context dev)
{
assert(dev != NULL);
return dev->ec;
}
float ecezo_get_tds(const ecezo_context dev)
{
assert(dev != NULL);
return dev->tds;
}
float ecezo_get_salinity(const ecezo_context dev)
{
assert(dev != NULL);
return dev->salinity;
}
float ecezo_get_sg(const ecezo_context dev)
{
assert(dev != NULL);
return dev->sg;
}
upm_result_t ecezo_calibrate(const ecezo_context dev, ECEZO_CALIBRATION_T cal,
float ec)
{
assert(dev != NULL);
char cmdBuffer[ECEZO_MAX_BUFFER_LEN];
switch(cal)
{
case ECEZO_CALIBRATE_CLEAR:
snprintf(cmdBuffer, ECEZO_MAX_BUFFER_LEN, "cal,clear");
break;
case ECEZO_CALIBRATE_DRY:
snprintf(cmdBuffer, ECEZO_MAX_BUFFER_LEN, "cal,dry");
break;
case ECEZO_CALIBRATE_ONE:
snprintf(cmdBuffer, ECEZO_MAX_BUFFER_LEN, "cal,one,%f", ec);
break;
case ECEZO_CALIBRATE_LOW:
snprintf(cmdBuffer, ECEZO_MAX_BUFFER_LEN, "cal,low,%f", ec);
break;
case ECEZO_CALIBRATE_HIGH:
snprintf(cmdBuffer, ECEZO_MAX_BUFFER_LEN, "cal,high,%f", ec);
break;
default:
// should be able to happen
printf("%s: invalid cal parameter\n", __FUNCTION__);
return UPM_ERROR_INVALID_PARAMETER;
}
return ecezo_send_command(dev, cmdBuffer, NULL, 0);
}