Z-Uno as a modem (AT commands)

This sketch implements modem feature in Z-Uno to allow configure and use Z-Uno via AT commands like any other RF modem (like Bluetooth HM-10 or XBee shields). Using AT commands you can set up the number of channels, association groups, report values and receive Set commands. Just load this sketch in Z-Uno and use AT commands to communicate with it from another MCU (like Arduino) or even from a PC.
CommandResponseDescription
^AT_CLEAN_CHANNELS$CLEAN_CHANNELS_OKClean channel information and start re-configuring Z-Uno channels. This should be the first AT command after Z-Uno boots.
^AT_ADD_CHANNEL,type,sensor_type,size_precision$ADD_CHANNEL_OKAdd one more channel to Z-Uno. Type, ? and ? are described in ZUNO_ADD_CHANNEL() reference
^AT_ADD_ASSOCIATION,type$ADD_ASSOCIATION_OKAdd one more association group to Z-Uno. Type is described in ZUNO_ADD_ASSOCIATION() reference
^AT_CONFIG_FLAGS,flags$CFG_FLAGS_OKSet Z-Uno configuration flags.
^AT_CONFIG_COMMIT$COMMIT_CONFIG,stateFinalize Z-Uno configuration and apply them. state tells you if it was applied or not. See ZUNO_GET_CONFIG_STATE() reference
^AT_LEARN,startStop$LEARN_OKEnable/disable learn mode:startStop equals 1 to start, 0 to abort learn mode.
^AT_CHANNEL_SET,channel,value$CHANNEL_SET_OKSet value of the channel
^AT_CHANNEL_GET,channel$CHANNEL_VALUE,valueGet value of the channel
^AT_SEND_REPORT,channel$SEND_REPORT_OKSends an unsolicited report from channel. Same as to call zunoSendReport(channel)
^AT_SEND_ASSOC$SEND_ASSOCIATION_OKzunoSendAssociationCommand()
All parameters and values are in decimal. No space before and after comma. Channels and association groups starts from 1.
Note that on Z-Uno reboot the last applied configuration is loaded. But on Z-Uno exclusion the configuration is cleared. Set the configuration before entering the learn mode for inclusion. Example of data communications

^AT_CLEAN_CHANNELS$start configuration
^CLEAN_CHANNELS_OK$
^AT_ADD_CHANNEL,01,00,00$add Switch Binary channel
^ADD_CHANNEL_OK$
^AT_ADD_CHANNEL,02,00,00$add Switch Multilevel channel
^ADD_CHANNEL_OK$
^AT_ADD_CHANNEL,04,01,66$add Sensor Multilevel channel, type temperature, size is two bytes, two bytes precision, celsius: 66 = 0x42 = 2 | (0 << 3) | (2 << 5)
ADD_CH^ADD_CHANNEL_OK$ANNEL_OK
^AT_ADD_ASSOCIATION,01$add one association group to set values
^ADD_ASSOCIATION_OK$
^AT_ADD_ASSOCIATION,04$add one association group to control door lock
^ADD_ASSOCIATION_OK$
^AT_CONFIG_COMMIT$apply changes
^COMMIT_CONFIG,2$applied

^AT_LEARN$,10start learn mode for 10 seconds (to exclude and include)
^LEARN_OK$

^AT_CHANNEL_SET,1,0$set channel 1 to Off
^CHANNEL_SET_OK$

^AT_CHANNEL_SET,2,50$set channel 2 to 50%
^CHANNEL_SET_OK$

^AT_CHANNEL_SET,3,2545$set channel 3 to 25.45°C
^CHANNEL_SET_OK$

^AT_SEND_ASSOC,1,1,255,0$send to On group 1
^SEND_ASSOCIATION_OK$

^AT_SEND_ASSOC,2,4,0,0$send to Close group 2
^SEND_ASSOCIATION_OK$

^CHANNEL_CHANGED,1,255$received 255 (On) in channel 1

^CHANNEL_CHANGED,2,30$received 30% in channel 2
  • Z-Uno board
  • your device that will use USB or UART to talk to Z-Uno
USB, UART0 and UART1 are supported. Change MY_SERIAL definition in the sketch to select the desired serial port.
/*
 * Z-Wave slave device fully controlled via Serial (USB/UART) using AT-Commands
 */
 
// Z-Uno can use any serial interface
// You can select
// Serial for USB
// Serial0 for UART0
// Serial1 fot UART1 
#define MY_SERIAL Serial1

#define MAX_CHANNELS 32 // The maximum number of channels supported by Z-Uno 

ZUNO_ENABLE(WITH_CC_SWITCH_MULTILEVEL WITH_CC_METER WITH_CC_SENSOR_BINARY WITH_CC_SWITCH_COLOR WITH_CC_SENSOR_MULTILEVEL WITH_CC_DOORLOCK WITH_CC_SWITCH_BINARY WITH_CC_NOTIFICATION WITH_CC_THERMOSTAT);
// Commands

ZUNO_DYNAMIC_CHANNELS(MAX_CHANNELS);

enum {
  AT_CMD_CLEAN_CHANNELS,
  AT_CMD_ADD_CHANNEL,
  AT_CMD_ADD_ASSOCIATION,
  AT_CMD_CONFIG_FLAGS,
  AT_CMD_LEARN,
  AT_CMD_CONFIG_COMMIT,
  AT_CMD_CHANNEL_SET,
  AT_CMD_CHANNEL_GET,
  AT_CMD_SEND_REPORT,
  AT_CMD_SEND_ASSOC,
  AT_SET_ZWAVE_CHANNEL
};
// Parser state machine
enum {
  AT_STATE_WAITSTART,
  AT_STATE_WAITCMD,
  AT_STATE_WAITPARAM
};

// Global data
// We use global scope to reduce stack usage
byte state = AT_STATE_WAITSTART;      // state machine
byte cmd;                             // current command
long int param[4];                    // command parameters
byte param_count = 0;                 // number of bytes in parameters
byte resp_count = 0;                  // number of bytes in report
char tmp_buff[32];                    // temp buffer
byte tmp_len = 0;                     // temp buffer length
dword channel_to_update = 0;          // list of channels to update
dword g_mask;                         // mask for zunoCallback

// g_channels_data[CH_NUMBER].dwParam
void setup() {
  MY_SERIAL.begin(115200);
}

// Incoming commands parser
byte parseCmd() {
  tmp_buff[tmp_len] = '\0';
  tmp_len = 0;

  if (strcmp(tmp_buff, "AT_CLEAN_CHANNELS") == 0) {
    cmd = AT_CMD_CLEAN_CHANNELS;
  } else if (strcmp(tmp_buff, "AT_ADD_CHANNEL") == 0) {
    cmd = AT_CMD_ADD_CHANNEL;
  } else if (strcmp(tmp_buff, "AT_ADD_ASSOCIATION") == 0) {
    cmd = AT_CMD_ADD_ASSOCIATION;
  } else if (strcmp(tmp_buff, "AT_CONFIG_FLAGS") == 0) {
    cmd = AT_CMD_CONFIG_FLAGS;
  } else if (strcmp(tmp_buff, "AT_LEARN") == 0) {
    cmd = AT_CMD_LEARN;
  } else if (strcmp(tmp_buff, "AT_CONFIG_COMMIT") == 0) {
    cmd = AT_CMD_CONFIG_COMMIT;
  } else if (strcmp(tmp_buff, "AT_CHANNEL_SET") == 0) {
    cmd = AT_CMD_CHANNEL_SET;
  } else if (strcmp(tmp_buff, "AT_CHANNEL_GET") == 0) {
    cmd = AT_CMD_CHANNEL_GET;
  } else if (strcmp(tmp_buff, "AT_SEND_ASSOC") == 0) {
    cmd = AT_CMD_SEND_ASSOC;
  } else if (strcmp(tmp_buff, "AT_SEND_REPORT") == 0) {
    cmd = AT_CMD_SEND_REPORT;
  } else if (strcmp(tmp_buff, "AT_SET_ZWAVE_CHANNEL") == 0) {
    cmd = AT_SET_ZWAVE_CHANNEL;
  } 
  else {
    return FALSE;
  }

  return TRUE;
}

// Print respons as ^...$
void response(char *resp_name) {
  byte i;
  MY_SERIAL.print("^");
  MY_SERIAL.print(resp_name);
  for (i = 0; i < resp_count; i++) {
    MY_SERIAL.print(",");
    MY_SERIAL.print(param[i]);
  }
  MY_SERIAL.println("$");
  resp_count = 0;
}

// Process command
byte processCmd() {
  switch(cmd) {
    case AT_CMD_CLEAN_CHANNELS:
      ZUNO_START_CONFIG();
      response("CLEAN_CHANNELS_OK");
      break;
    case AT_CMD_ADD_CHANNEL:
      ZUNO_ADD_CHANNEL(byte(param[0]), byte(param[1]), byte(param[2]));
      response("ADD_CHANNEL_OK");
      break;
    case AT_CMD_ADD_ASSOCIATION:
      ZUNO_ADD_ASSOCIATION(byte(param[0]));
      response("ADD_ASSOCIATION_OK");
      break;  
    case AT_CMD_CONFIG_FLAGS:
      g_user_sketch.flags = byte(param[0]);
      response("CFG_FLAGS_OK");
      break;
    case AT_CMD_LEARN:
      zunoStartLearn(100, byte(param[0]));
      response("LEARN_OK");
      break;
    case AT_CMD_CONFIG_COMMIT:
      ZUNO_COMMIT_CONFIG();
      resp_count = 1;
      param[0] = ZUNO_GET_CONFIG_STATE();
      response("COMMIT_CONFIG");
      break;
    case AT_CMD_CHANNEL_SET:
      g_channels_data[byte(param[0]) - 1].dwParam = param[1];
      //channel_value[byte(param[0]) - 1] = param[1];
      response("CHANNEL_SET_OK");
      break;
    case AT_CMD_CHANNEL_GET:
      param[1] = g_channels_data[byte(param[0]) - 1].dwParam;
      //param[1] = channel_value[byte(param[0]) - 1];
      resp_count = 2;
      response("CHANNEL_VALUE");
      break;
    case AT_CMD_SEND_REPORT:
      zunoSendReport(byte(param[0]));
      response("SEND_REPORT_OK");
      break;
    case AT_CMD_SEND_ASSOC:
      zunoSendAssociationCommand(byte(param[0]), byte(param[1]), byte(param[2]), byte(param[3]));
      response("SEND_ASSOCIATION_OK");
      break;
    case AT_SET_ZWAVE_CHANNEL:
      ZUNO_SET_ZWCHANNEL(byte(param[0]));
      response("AT_SET_ZWAVE_CHANNEL_OK");
      break;  
    default:
      break;
  } 
  return 1;
}

// Fill command parameters
byte parseParam() {
  // Check if value is valid
  for(byte i = 0; i < tmp_len; i++) {
    if (tmp_buff[i] < '0' || tmp_buff[i] > '9') {
      return 0;
    }
  }
  tmp_buff[tmp_len] = '\0';
  tmp_len = 0;
  // Convert string representation to long
  param[param_count] = atol(tmp_buff);
  param_count++;
  return 1;
}

// Respond with error
void parseError() {
  state = AT_STATE_WAITSTART;
  MY_SERIAL.println("^SYNTAX_ERROR$");
}

// Read and parse AT command
// Uses a state machine for parsing
void parseAT() {
  while(MY_SERIAL.available()) {
    char symbol = MY_SERIAL.read();
    switch(state) {
      case AT_STATE_WAITSTART:
        if (symbol == '^') {
          tmp_len = 0;
          param_count = 0;
          resp_count = 0;
          state++;
        }     
        break;
      case AT_STATE_WAITCMD:
        if (symbol == ',') {
          if (!parseCmd()) {
            parseError();
          } else {
            state++;
          }
        } else if (symbol == '$') {
          if (parseCmd()) {
            processCmd();
          } else {
            parseError();
          }
          state = AT_STATE_WAITSTART;
        } else {
          tmp_buff[tmp_len] = symbol;
          tmp_len++;
        }
        break;
      case AT_STATE_WAITPARAM:
        if (symbol == ',') {  
          if (!parseParam()) {
            parseError();
          }
        } else if (symbol == '$') {
          if (parseParam()) {
            processCmd();
          } else {
            parseError();
          }
          state = AT_STATE_WAITSTART;
        } else {
          tmp_buff[tmp_len] = symbol;
          tmp_len++;
        }
        break;
    }
  }
}

// Push unsolicited channel value updates to the serial port
void makeUnsolicitedReports() {
  byte i = 0;
  g_mask = 1;
  while (channel_to_update != 0) {
    if (channel_to_update & g_mask) {
      resp_count = 2;
      param[0] = i + 1;
      param[1] = g_channels_data[i].dwParam;
      response("CHANNEL_CHANGED");
    }

    i++;
    channel_to_update &= ~(g_mask);
    g_mask <<= 1;
  }
}

// Parse AT commands and push updates in a loop
void loop() {
  parseAT();
  makeUnsolicitedReports();
  delay(100);
}
Download this sketch
This sketch make use of Z-Uno dynamic configuration provided by functions: ZUNO_START_CONFIG(), ZUNO_ADD_CHANNEL(), ZUNO_ADD_ASSOCIATION(), ZUNO_COMMIT_CONFIG(), ZUNO_GET_CONFIG_STATE(), zunoStartLearn(), zunoSendAssociationCommand().