blob: d742f5f87f4fc75eda97e251bba85ea0f556b7d5 [file] [log] [blame]
/*
* Copyright (c) 2016, CESAR.
* All rights reserved.
*
* This software may be modified and distributed under the terms
* of the BSD license. See the LICENSE file for details.
*
*/
/* Flag to enable debug logs via Serial */
#define KNOT_DEBUG_ENABLED 0
#include <stdint.h>
#ifdef ARDUINO
#include <Arduino.h>
#include <hal/avr_errno.h>
#include <hal/avr_unistd.h>
#include <hal/avr_log.h>
#define CLEAR_EEPROM_PIN 7
#define PIN_LED_STATUS 6 //LED used to show thing status
#endif
#include <hal/storage.h>
#include <hal/nrf24.h>
#include <hal/comm.h>
#include <hal/gpio_avr.h>
#include <hal/time.h>
#include "knot_thing_protocol.h"
#include "knot_thing_main.h"
#include "knot_thing_config.h"
/* KNoT protocol client states */
#define STATE_DISCONNECTED 0
#define STATE_ACCEPTING 1
#define STATE_CONNECTED 2
#define STATE_AUTHENTICATING 3
#define STATE_REGISTERING 4
#define STATE_SCHEMA 5
#define STATE_SCHEMA_RESP 6
#define STATE_ONLINE 7
#define STATE_RUNNING 8
#define STATE_ERROR 9
/* Intervals for LED blinking */
#define LONG_INTERVAL 10000
#define SHORT_INTERVAL 1000
/* Number of times LED is blinking */
#define BLINK_DISCONNECTED 100
#define BLINK_STABLISHING 2
#define BLINK_ONLINE 1
/* Periods for LED blinking in HALT conditions */
#define NAME_ERROR 50
#define COMM_ERROR 100
#define AUTH_ERROR 250
/* Time that the clear eeprom button needs to be pressed */
#define BUTTON_PRESSED_TIME 5000
/* KNoT MTU */
#define MTU 256
#ifndef MIN
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
#endif
/* Retransmission timeout in ms */
#define RETRANSMISSION_TIMEOUT 20000
static knot_msg msg;
static struct nrf24_config config = { .mac = 0, .channel = 76 , .name = NULL};
static unsigned long clear_time;
static uint32_t last_timeout;
static uint32_t unreg_timeout;
static int sock = -1;
static int cli_sock = -1;
static bool schema_flag = false;
static uint8_t enable_run = 0, msg_sensor_id = 0;
/*
* FIXME: Thing address should be received via NFC
* Mac address must be stored in big endian format
*/
void(* reset_function) (void) = 0; //declare reset function @ address 0
static void set_nrf24MAC(void)
{
hal_getrandom(config.mac.address.b, sizeof(struct nrf24_mac));
hal_storage_write_end(HAL_STORAGE_ID_MAC, &config.mac,
sizeof(struct nrf24_mac));
}
static void thing_disconnect_exit(void)
{
/* reset EEPROM (UUID/Token) and generate new MAC addr */
hal_storage_reset_end();
set_nrf24MAC();
/* close connection */
knot_thing_protocol_exit();
/* reset thing */
reset_function();
}
static void verify_clear_data(void)
{
if (!hal_gpio_digital_read(CLEAR_EEPROM_PIN)) {
if (clear_time == 0)
clear_time = hal_time_ms();
if (hal_timeout(hal_time_ms(), clear_time, BUTTON_PRESSED_TIME)) {
thing_disconnect_exit();
}
} else
clear_time = 0;
}
static void halt_blinking_led(uint32_t period)
{
while (1) {
hal_gpio_digital_write(PIN_LED_STATUS, 0);
hal_delay_ms(period);
hal_gpio_digital_write(PIN_LED_STATUS, 1);
hal_delay_ms(period);
verify_clear_data();
}
}
static int init_connection(void)
{
#if (KNOT_DEBUG_ENABLED == 1)
char macString[25] = {0};
nrf24_mac2str(&config.mac, macString);
hal_log_str("MAC");
hal_log_str(macString);
#endif
if (hal_comm_init("NRF0", &config) < 0)
halt_blinking_led(COMM_ERROR);
sock = hal_comm_socket(HAL_COMM_PF_NRF24, HAL_COMM_PROTO_RAW);
if (sock < 0)
halt_blinking_led(COMM_ERROR);
clear_time = 0;
enable_run = 1;
last_timeout = 0;
return 0;
}
int knot_thing_protocol_init(const char *thing_name)
{
hal_gpio_pin_mode(PIN_LED_STATUS, OUTPUT);
hal_gpio_pin_mode(CLEAR_EEPROM_PIN, INPUT_PULLUP);
if (thing_name == NULL)
halt_blinking_led(NAME_ERROR);
config.name = (const char *) thing_name;
/* Set mac address if it's invalid on eeprom */
hal_storage_read_end(HAL_STORAGE_ID_MAC, &config.mac,
sizeof(struct nrf24_mac));
/* MAC criteria: less significant 32-bits should not be zero */
if (!(config.mac.address.uint64 & 0x00000000ffffffff)) {
hal_storage_reset_end();
set_nrf24MAC();
}
config.id = config.mac.address.uint64;
return init_connection();
}
void knot_thing_protocol_exit(void)
{
hal_comm_close(cli_sock);
hal_comm_close(sock);
hal_comm_deinit();
enable_run = 0;
}
/*
* The function receives the number of times LED shall blink.
* For each number n, the status LED should flash 2 * n
* (once to light, another to turn off)
*/
static void led_status(uint8_t status)
{
static uint32_t previous_status_time = 0;
static uint16_t status_interval;
static uint8_t nblink, led_state, previous_led_state = LOW;
uint32_t current_status_time = hal_time_ms();
/*
* If the LED has lit and off twice the state,
* a set a long interval
*/
if (nblink >= (status * 2)) {
nblink = 0;
status_interval = LONG_INTERVAL;
hal_gpio_digital_write(PIN_LED_STATUS, 0);
}
/*
* Ensures that whenever the status changes,
* the blink starts by turning on the LED
**/
if (status != previous_led_state) {
previous_led_state = status;
led_state = LOW;
}
if ((current_status_time - previous_status_time) >= status_interval) {
previous_status_time = current_status_time;
led_state = !led_state;
hal_gpio_digital_write(PIN_LED_STATUS, led_state);
nblink++;
status_interval = SHORT_INTERVAL;
}
}
static int send_unregister(void)
{
/* send KNOT_MSG_UNREGISTER_RESP message */
msg.hdr.type = KNOT_MSG_UNREGISTER_RESP;
msg.hdr.payload_len = 0;
if (hal_comm_write(cli_sock, &(msg.buffer),
sizeof(msg.hdr) + msg.hdr.payload_len) < 0)
return -1;
unreg_timeout = hal_time_ms();
return 0;
}
static int send_register(void)
{
/*
* KNOT_MSG_REGISTER_REQ PDU should fit in nRF24 MTU in order
* to avoid frame segmentation. Re-transmission may happen
* frequently at noisy environments or if the remote is not ready.
*/
uint8_t name_len = NRF24_MTU - (sizeof(msg.reg.hdr) +
sizeof(msg.reg.id));
name_len = MIN(name_len, strlen(config.name));
msg.hdr.type = KNOT_MSG_REGISTER_REQ;
msg.reg.id = config.mac.address.uint64; /* Maps id to nRF24 MAC */
strncpy(msg.reg.devName, config.name, name_len);
msg.hdr.payload_len = name_len + sizeof(msg.reg.id);
if (hal_comm_write(cli_sock, &(msg.buffer),
sizeof(msg.hdr) + msg.hdr.payload_len) < 0)
return -1;
return 0;
}
static int read_register(void)
{
ssize_t nbytes;
nbytes = hal_comm_read(cli_sock, &(msg.buffer), KNOT_MSG_SIZE);
if (nbytes <= 0)
return nbytes;
if (msg.hdr.type == KNOT_MSG_UNREGISTER_REQ) {
return send_unregister();
}
if (msg.hdr.type != KNOT_MSG_REGISTER_RESP)
return -1;
if (msg.cred.result != KNOT_SUCCESS)
return -1;
hal_storage_write_end(HAL_STORAGE_ID_UUID, msg.cred.uuid,
KNOT_PROTOCOL_UUID_LEN);
hal_storage_write_end(HAL_STORAGE_ID_TOKEN, msg.cred.token,
KNOT_PROTOCOL_TOKEN_LEN);
return 0;
}
static int read_auth(void)
{
ssize_t nbytes;
nbytes = hal_comm_read(cli_sock, &(msg.buffer), KNOT_MSG_SIZE);
if (nbytes <= 0)
return nbytes;
if (msg.hdr.type == KNOT_MSG_UNREGISTER_REQ) {
return send_unregister();
}
if (msg.hdr.type != KNOT_MSG_AUTH_RESP)
return -1;
if (msg.action.result != KNOT_SUCCESS)
return -1;
return 0;
}
static int send_schema(void)
{
int8_t err;
err = knot_thing_create_schema(msg_sensor_id, &(msg.schema));
if (err < 0)
return err;
if (hal_comm_write(cli_sock, &(msg.buffer),
sizeof(msg.hdr) + msg.hdr.payload_len) < 0)
/* TODO create a better error define in the protocol */
return KNOT_ERROR_UNKNOWN;
return KNOT_SUCCESS;
}
static int msg_set_config(uint8_t sensor_id)
{
int8_t err;
err = knot_thing_config_data_item(msg.config.sensor_id,
msg.config.values.event_flags,
msg.config.values.time_sec,
&(msg.config.values.lower_limit),
&(msg.config.values.upper_limit));
if (err)
return KNOT_ERROR_UNKNOWN;
msg.item.sensor_id = sensor_id;
msg.hdr.type = KNOT_MSG_CONFIG_RESP;
msg.hdr.payload_len = sizeof(msg.item.sensor_id);
if (hal_comm_write(cli_sock, &(msg.buffer),
sizeof(msg.hdr) + msg.hdr.payload_len) < 0)
return -1;
return 0;
}
static int msg_set_data(uint8_t sensor_id)
{
int8_t err;
err = knot_thing_data_item_write(sensor_id, &(msg.data));
/*
* GW must be aware if the data was succesfully set, so we resend
* the request only changing the header type
*/
msg.hdr.type = KNOT_MSG_DATA_RESP;
/* TODO: Improve error handling: Sensor not found, invalid data, etc */
if (err < 0)
msg.hdr.type = KNOT_ERROR_UNKNOWN;
if (hal_comm_write(cli_sock, &(msg.buffer),
sizeof(msg.hdr) + msg.hdr.payload_len) < 0)
return -1;
return 0;
}
static int msg_get_data(uint8_t sensor_id)
{
int8_t err;
err = knot_thing_data_item_read(sensor_id, &(msg.data));
if (err == -2)
return err;
msg.hdr.type = KNOT_MSG_DATA;
if (err < 0)
msg.hdr.type = KNOT_ERROR_UNKNOWN;
msg.data.sensor_id = sensor_id;
if (hal_comm_write(cli_sock, &(msg.buffer),
sizeof(msg.hdr) + msg.hdr.payload_len) < 0)
return -1;
return 0;
}
static inline int is_uuid(const char *string)
{
return (string != NULL && string[8] == '-' &&
string[13] == '-' && string[18] == '-' && string[23] == '-');
}
static int8_t mgmt_read(void)
{
uint8_t buffer[MTU];
struct mgmt_nrf24_header *mhdr = (struct mgmt_nrf24_header *) buffer;
ssize_t retval;
retval = hal_comm_read(sock, buffer, sizeof(buffer));
if (retval < 0)
return retval;
/* Return/ignore if it is not an event? */
if (!(mhdr->opcode & 0x0200))
return -EPROTO;
switch (mhdr->opcode) {
case MGMT_EVT_NRF24_DISCONNECTED:
return -ENOTCONN;
}
return 0;
}
static void read_online_messages(void)
{
if (hal_comm_read(cli_sock, &(msg.buffer), KNOT_MSG_SIZE) <= 0)
return;
/* There is a message to read */
switch (msg.hdr.type) {
case KNOT_MSG_SET_CONFIG:
msg_set_config(msg.config.sensor_id);
break;
case KNOT_MSG_SET_DATA:
msg_set_data(msg.data.sensor_id);
break;
case KNOT_MSG_GET_DATA:
msg_get_data(msg.item.sensor_id);
break;
case KNOT_MSG_DATA_RESP:
hal_log_str("DT RSP");
if (msg.action.result != KNOT_SUCCESS) {
hal_log_str("DT R ERR");
msg_get_data(msg.item.sensor_id);
}
break;
case KNOT_MSG_UNREGISTER_REQ:
send_unregister();
break;
default:
/* Invalid command, ignore */
break;
}
}
int knot_thing_protocol_run(void)
{
static uint8_t run_state = STATE_DISCONNECTED;
struct nrf24_mac peer;
int8_t retval;
/*
* Verifies if the button for eeprom clear is pressed for more than 5s
*/
verify_clear_data();
if (enable_run == 0) {
return -1;
}
if (run_state >= STATE_CONNECTED) {
if (mgmt_read() == -ENOTCONN)
run_state = STATE_DISCONNECTED;
}
if (unreg_timeout && hal_timeout(hal_time_ms(), unreg_timeout, 10000) > 0)
thing_disconnect_exit();
/* Network message handling state machine */
switch (run_state) {
case STATE_DISCONNECTED:
/* Internally listen starts broadcasting presence*/
led_status(BLINK_DISCONNECTED);
hal_comm_close(cli_sock);
hal_log_str("DISC");
if (hal_comm_listen(sock) < 0) {
break;
}
run_state = STATE_ACCEPTING;
hal_log_str("ACCT");
break;
case STATE_ACCEPTING:
/*
* Try to accept GW connection request. EAGAIN means keep
* waiting, less then 0 means error and greater then 0 success
*/
led_status(BLINK_DISCONNECTED);
cli_sock = hal_comm_accept(sock, (void *) &peer);
if (cli_sock == -EAGAIN)
break;
else if (cli_sock < 0) {
run_state = STATE_DISCONNECTED;
break;
}
run_state = STATE_CONNECTED;
hal_log_str("CONN");
break;
case STATE_CONNECTED:
/*
* If uuid/token were found, read the addresses and send
* the auth request, otherwise register request
*/
led_status(BLINK_STABLISHING);
hal_storage_read_end(HAL_STORAGE_ID_UUID, &(msg.auth.uuid),
KNOT_PROTOCOL_UUID_LEN);
hal_storage_read_end(HAL_STORAGE_ID_TOKEN, &(msg.auth.token),
KNOT_PROTOCOL_TOKEN_LEN);
if (is_uuid(msg.auth.uuid)) {
run_state = STATE_AUTHENTICATING;
hal_log_str("AUTH");
msg.hdr.type = KNOT_MSG_AUTH_REQ;
msg.hdr.payload_len = KNOT_PROTOCOL_UUID_LEN +
KNOT_PROTOCOL_TOKEN_LEN;
if (hal_comm_write(cli_sock, &(msg.buffer),
sizeof(msg.hdr) + msg.hdr.payload_len) < 0)
run_state = STATE_ERROR;
} else {
hal_log_str("REG");
run_state = STATE_REGISTERING;
if (send_register() < 0)
run_state = STATE_ERROR;
}
last_timeout = hal_time_ms();
break;
/*
* Authenticating, Resgistering cases waits (without blocking)
* for an response of the respective requests, -EAGAIN means there was
* nothing to read so we ignore it, less then 0 an error and 0 success
*/
case STATE_AUTHENTICATING:
led_status(BLINK_STABLISHING);
retval = read_auth();
if (retval == KNOT_SUCCESS) {
run_state = STATE_ONLINE;
hal_log_str("ONLN");
/* Checks if all the schemas were sent to the GW and */
hal_storage_read_end(HAL_STORAGE_ID_SCHEMA_FLAG,
&schema_flag, sizeof(schema_flag));
if (!schema_flag)
run_state = STATE_SCHEMA;
}
else if (retval != -EAGAIN)
halt_blinking_led(AUTH_ERROR);
else if (hal_timeout(hal_time_ms(), last_timeout,
RETRANSMISSION_TIMEOUT) > 0)
run_state = STATE_CONNECTED;
break;
case STATE_REGISTERING:
led_status(BLINK_STABLISHING);
retval = read_register();
if (!retval)
run_state = STATE_SCHEMA;
else if (retval != -EAGAIN)
run_state = STATE_ERROR;
else if (hal_timeout(hal_time_ms(), last_timeout,
RETRANSMISSION_TIMEOUT) > 0)
run_state = STATE_CONNECTED;
break;
/*
* STATE_SCHEMA tries to send an schema and go to STATE_SCHEMA_RESP to
* wait for the ack of this schema. If there is no schema for that
* msg_sensor_id, increments and stays in the STATE_SCHEMA. If an
* error occurs, goes to STATE_ERROR.
*/
case STATE_SCHEMA:
led_status(BLINK_STABLISHING);
hal_log_str("SCH");
retval = send_schema();
switch (retval) {
case KNOT_SUCCESS:
last_timeout = hal_time_ms();
run_state = STATE_SCHEMA_RESP;
break;
case KNOT_ERROR_UNKNOWN:
run_state = STATE_ERROR;
break;
case KNOT_SCHEMA_EMPTY:
case KNOT_INVALID_DEVICE:
run_state = STATE_SCHEMA;
msg_sensor_id++;
break;
default:
run_state = STATE_ERROR;
break;
}
break;
/*
* Receives the ack from the GW and returns to STATE_SCHEMA to send the
* next schema. If it was the ack for the last schema, goes to
* STATE_ONLINE. If it is not a KNOT_MSG_SCHEMA_RESP, ignores. If the
* result was not KNOT_SUCCESS, goes to STATE_ERROR.
*/
case STATE_SCHEMA_RESP:
led_status(BLINK_STABLISHING);
hal_log_str("SCH_R");
if (hal_comm_read(cli_sock, &(msg.buffer), KNOT_MSG_SIZE) > 0) {
if (msg.hdr.type == KNOT_MSG_UNREGISTER_REQ) {
send_unregister();
break;
}
if (msg.hdr.type != KNOT_MSG_SCHEMA_RESP &&
msg.hdr.type != KNOT_MSG_SCHEMA_END_RESP)
break;
if (msg.action.result != KNOT_SUCCESS) {
run_state = STATE_SCHEMA;
break;
}
if (msg.hdr.type != KNOT_MSG_SCHEMA_END_RESP) {
run_state = STATE_SCHEMA;
msg_sensor_id++;
break;
}
/* All the schemas were sent to GW */
schema_flag = true;
hal_storage_write_end(HAL_STORAGE_ID_SCHEMA_FLAG,
&schema_flag, sizeof(schema_flag));
run_state = STATE_ONLINE;
hal_log_str("ONLN");
msg_sensor_id = 0;
} else if (hal_timeout(hal_time_ms(), last_timeout,
RETRANSMISSION_TIMEOUT) > 0)
run_state = STATE_SCHEMA;
break;
case STATE_ONLINE:
led_status(BLINK_ONLINE);
read_online_messages();
msg_sensor_id++;
msg_get_data(msg_sensor_id);
hal_log_str("DT");
if (msg_sensor_id >= KNOT_THING_DATA_MAX) {
msg_sensor_id = 0;
run_state = STATE_RUNNING;
hal_log_str("RUN");
}
break;
case STATE_RUNNING:
led_status(BLINK_ONLINE);
read_online_messages();
/* If some event ocurred send msg_data */
if (knot_thing_verify_events(&(msg.data)) == 0) {
if (hal_comm_write(cli_sock, &(msg.buffer),
sizeof(msg.hdr) + msg.hdr.payload_len) < 0) {
hal_log_str("DT ERR");
hal_comm_write(cli_sock, &(msg.buffer),
sizeof(msg.hdr) + msg.hdr.payload_len);
} else {
hal_log_str("DT");
}
}
break;
case STATE_ERROR:
hal_gpio_digital_write(PIN_LED_STATUS, 1);
hal_log_str("ERR");
/* TODO: Review error state handling */
run_state = STATE_DISCONNECTED;
hal_delay_ms(1000);
break;
default:
hal_log_str("INV");
run_state = STATE_DISCONNECTED;
break;
}
return 0;
}