diff --git a/Makefile b/Makefile index 2fa7ece65..7bd04fb13 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ PROJECT_VER_MAJOR := 1 PROJECT_VER_MINOR := 0 PROJECT_VER_PATCH := 0 PROJECT_VER_BUILD := beta -PROJECT_VER_BUILD_NUM := 8 +PROJECT_VER_BUILD_NUM := 9 BOARD_PYPORTAL := samd51-pyportal BOARD_METRO_AIRLIFT := samd51-metro-airlift diff --git a/library.properties b/library.properties index 6f6047777..0e6312908 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Adafruit WipperSnapper -version=1.0.0-beta.8 +version=1.0.0-beta.9 author=Adafruit maintainer=Adafruit sentence=Arduino library to access WipperSnapper diff --git a/src/.vscode/settings.json b/src/.vscode/settings.json new file mode 100644 index 000000000..29c7362ca --- /dev/null +++ b/src/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "C_Cpp.dimInactiveRegions": false, + "files.associations": { + "__config": "cpp" + } +} \ No newline at end of file diff --git a/src/Wippersnapper.cpp b/src/Wippersnapper.cpp index a38a89da2..e424a0266 100644 --- a/src/Wippersnapper.cpp +++ b/src/Wippersnapper.cpp @@ -383,8 +383,14 @@ bool cbSignalMsg(pb_istream_t *stream, const pb_field_t *field, void **arg) { // decode each ConfigurePinRequest sub-message if (!pb_decode(stream, wippersnapper_pin_v1_ConfigurePinRequests_fields, &msg)) { - WS_DEBUG_PRINTLN("ERROR: Could not decode CreateSign2alRequest") + WS_DEBUG_PRINTLN("ERROR: Could not decode CreateSignalRequest") is_success = false; + WS.pinCfgCompleted = false; + } + // If this is the initial configuration + if (!WS.pinCfgCompleted) { + WS_DEBUG_PRINTLN("Initial Pin Configuration Complete!"); + WS.pinCfgCompleted = true; } } else if (field->tag == wippersnapper_signal_v1_CreateSignalRequest_pin_events_tag) { @@ -503,11 +509,12 @@ bool Wippersnapper::encodePinEvent( /**************************************************************************/ /*! @brief Called when broker responds to a device's publish across - the registration topic. + the registration topic. */ /**************************************************************************/ void cbRegistrationStatus(char *data, uint16_t len) { - WS._registerBoard->decodeRegMsg(data, len); + // call decoder for registration response msg + WS.decodeRegistrationResp(data, len); } /**************************************************************************/ @@ -734,6 +741,18 @@ bool Wippersnapper::buildWSTopics() { strlen(_device_uid) + strlen(TOPIC_DESCRIPTION) + strlen("status") + strlen("broker") + 1); + // Registration status completion topic + WS._topic_description_status_complete = (char *)malloc( + sizeof(char) * strlen(WS._username) + +strlen("/wprsnpr/") + + strlen(_device_uid) + strlen(TOPIC_DESCRIPTION) + strlen("status") + + strlen("/device/complete") + 1); + + // Topic to signal pin configuration complete from device to broker + WS._topic_device_pin_config_complete = (char *)malloc( + sizeof(char) * strlen(WS._username) + +strlen("/") + strlen(_device_uid) + + strlen("/wprsnpr/") + strlen(TOPIC_SIGNALS) + + strlen("device/pinConfigComplete") + 1); + // Topic for signals from device to broker WS._topic_signal_device = (char *)malloc( sizeof(char) * strlen(WS._username) + +strlen("/") + strlen(_device_uid) + @@ -768,6 +787,19 @@ bool Wippersnapper::buildWSTopics() { is_success = false; } + // Create registration status complete topic + if (WS._topic_description_status_complete) { + strcpy(WS._topic_description_status_complete, WS._username); + strcat(WS._topic_description_status_complete, "/wprsnpr/"); + strcat(WS._topic_description_status_complete, _device_uid); + strcat(WS._topic_description_status_complete, TOPIC_DESCRIPTION); + strcat(WS._topic_description_status_complete, "status"); + strcat(WS._topic_description_status_complete, "/device/complete"); + } else { // malloc failed + WS._topic_description_status_complete = 0; + is_success = false; + } + // Create device-to-broker signal topic if (WS._topic_signal_device) { strcpy(WS._topic_signal_device, WS._username); @@ -780,6 +812,18 @@ bool Wippersnapper::buildWSTopics() { is_success = false; } + // Create device-to-broker signal topic + if (WS._topic_device_pin_config_complete) { + strcpy(WS._topic_device_pin_config_complete, WS._username); + strcat(WS._topic_device_pin_config_complete, "/wprsnpr/"); + strcat(WS._topic_device_pin_config_complete, _device_uid); + strcat(WS._topic_device_pin_config_complete, TOPIC_SIGNALS); + strcat(WS._topic_device_pin_config_complete, "device/pinConfigComplete"); + } else { // malloc failed + WS._topic_device_pin_config_complete = 0; + is_success = false; + } + // Create broker-to-device signal topic if (WS._topic_signal_brkr) { strcpy(WS._topic_signal_brkr, WS._username); @@ -892,20 +936,27 @@ void Wippersnapper::haltError(String error) { /**************************************************************************/ /*! - @brief Sends board description message to Wippersnapper - @param retries - Amount of times to retry registration process. + @brief Attempts to register hardware with Adafruit.io WipperSnapper. @returns True if successful, False otherwise. */ /**************************************************************************/ -bool Wippersnapper::registerBoard(uint8_t retries = 10) { +bool Wippersnapper::registerBoard() { bool is_success = false; - WS_DEBUG_PRINTLN("registerBoard()"); - // Create new board - _registerBoard = new Wippersnapper_Registration(); - // Run the FSM for the registration process - is_success = _registerBoard->processRegistration(); - return is_success; + WS_DEBUG_PRINTLN("Registering hardware with IO..."); + + // Encode and publish registration request message to broker + runNetFSM(); + feedWDT(); + WS_DEBUG_PRINT("Encoding registration request..."); + if (!encodePubRegistrationReq()) + return false; + + // Blocking, attempt to obtain broker's response message + runNetFSM(); + feedWDT(); + pollRegistrationResp(); + + return true; } /**************************************************************************/ @@ -1034,17 +1085,60 @@ void Wippersnapper::connect() { setStatusLEDColor(LED_CONNECTED); // Register hardware with Wippersnapper - WS_DEBUG_PRINTLN("Registering Board...") + WS_DEBUG_PRINTLN("Registering hardware with WipperSnapper...") setStatusLEDColor(LED_IO_REGISTER_HW); - if (!registerBoard(10)) { - haltError("Unable to register board with Wippersnapper."); + + if (!registerBoard()) { + haltError("Unable to register with WipperSnapper."); } - feedWDT(); // Hardware registered with IO, feed WDT + feedWDT(); // Hardware registered with IO, feed WDT */ + WS_DEBUG_PRINTLN("Registered with WipperSnapper."); + + WS_DEBUG_PRINTLN("Polling for configuration message..."); + WS._mqtt->processPackets(2000); + + // did we get and process the registration message? + if (!WS.pinCfgCompleted) + haltError("Did not get configuration message from broker, resetting..."); + + // Publish that we have completed the configuration workflow + feedWDT(); + runNetFSM(); + publishPinConfigComplete(); - WS_DEBUG_PRINTLN("Registered board with Wippersnapper."); + // Run application statusLEDBlink(WS_LED_STATUS_CONNECTED); + WS_DEBUG_PRINTLN( + "Registration and configuration complete!\nRunning application..."); +} - WS._mqtt->processPackets(1000); +void Wippersnapper::publishPinConfigComplete() { + // Publish that we've set up the pins and are ready to run + wippersnapper_signal_v1_SignalResponse msg = + wippersnapper_signal_v1_SignalResponse_init_zero; + msg.which_payload = + wippersnapper_signal_v1_SignalResponse_configuration_complete_tag; + msg.payload.configuration_complete = true; + + // encode registration request message + uint8_t _message_buffer[128]; + pb_ostream_t _msg_stream = + pb_ostream_from_buffer(_message_buffer, sizeof(_message_buffer)); + + bool _status = + pb_encode(&_msg_stream, + wippersnapper_description_v1_RegistrationComplete_fields, &msg); + size_t _message_len = _msg_stream.bytes_written; + + // verify message encoded correctly + if (!_status) + haltError("Could not encode, resetting..."); + + // Publish message + WS_DEBUG_PRINTLN("Publishing to pin config complete..."); + WS.publish(WS._topic_device_pin_config_complete, _message_buffer, + _message_len, 1); + WS_DEBUG_PRINTLN("Completed registration process, configuration next!"); } /**************************************************************************/ @@ -1055,12 +1149,8 @@ void Wippersnapper::connect() { /**************************************************************************/ ws_status_t Wippersnapper::run() { // Check networking - // TODO: Handle MQTT errors within the new netcode, or bring them outwards to - // another fsm runNetFSM(); feedWDT(); - - // keepalive pingBroker(); // Process all incoming packets from Wippersnapper MQTT Broker diff --git a/src/Wippersnapper.h b/src/Wippersnapper.h index a010893b9..81019c990 100644 --- a/src/Wippersnapper.h +++ b/src/Wippersnapper.h @@ -30,7 +30,6 @@ // Wippersnapper API Helpers #include "Wippersnapper_Boards.h" -#include "Wippersnapper_Registration.h" #include "components/statusLED/Wippersnapper_StatusLED_Colors.h" // Wippersnapper GPIO Components @@ -49,9 +48,10 @@ #include "Adafruit_SleepyDog.h" #endif -// Uncomment to use the staging IO server staging builds -// #define IO_MQTT_SERVER "io.adafruit.us" ///< Adafruit IO MQTT Server -// (Staging) +// Uncomment the following use the staging IO server // +//#define USE_STAGING +//#define IO_MQTT_SERVER "io.adafruit.us" ///< Adafruit IO MQTT Server + #define IO_MQTT_SERVER \ "io.adafruit.com" ///< Adafruit IO MQTT Server (Production) @@ -64,7 +64,7 @@ #endif #define WS_VERSION \ - "1.0.0-beta.8" ///< WipperSnapper app. version (semver-formatted) + "1.0.0-beta.9" ///< WipperSnapper app. version (semver-formatted) // Reserved Adafruit IO MQTT topics #define TOPIC_IO_THROTTLE "/throttle" ///< Adafruit IO Throttle MQTT Topic @@ -154,9 +154,8 @@ typedef enum { 4000 ///< Session keepalive interval time, in milliseconds #define WS_MQTT_MAX_PAYLOAD_SIZE \ - 256 ///< MAXIMUM expected payload size, in bytes + 300 ///< MAXIMUM expected payload size, in bytes -class Wippersnapper_Registration; class Wippersnapper_DigitalGPIO; class Wippersnapper_AnalogIO; class Wippersnapper_FS; @@ -207,8 +206,13 @@ class Wippersnapper { bool buildErrorTopics(); void subscribeErrorTopics(); - // Performs board registration FSM - bool registerBoard(uint8_t retries); + // Registration API + bool registerBoard(); + bool encodePubRegistrationReq(); + void decodeRegistrationResp(char *data, uint16_t len); + void pollRegistrationResp(); + // Configuration API + void publishPinConfigComplete(); // run() loop ws_status_t run(); @@ -249,8 +253,6 @@ class Wippersnapper { ws_board_status_t _boardStatus; ///< Hardware's registration status - Wippersnapper_Registration *_registerBoard = - NULL; ///< Instance of registration class Wippersnapper_DigitalGPIO *_digitalGPIO; ///< Instance of digital gpio class Wippersnapper_AnalogIO *_analogIO; ///< Instance of analog io class Wippersnapper_FS *_fileSystem; ///< Instance of filesystem class @@ -280,6 +282,8 @@ class Wippersnapper { wippersnapper_signal_v1_CreateSignalRequest _incomingSignalMsg; /*!< Incoming signal message from broker */ + bool pinCfgCompleted = false; + private: void _init(); @@ -299,7 +303,9 @@ class Wippersnapper { // MQTT topics char *_topic_description_status; /*!< MQTT subtopic carrying the description status resp. from the broker */ - char *_topic_signal_brkr; /*!< Wprsnpr->Device messages */ + char *_topic_description_status_complete; + char *_topic_device_pin_config_complete; + char *_topic_signal_brkr; /*!< Wprsnpr->Device messages */ Adafruit_MQTT_Subscribe *_topic_description_sub; /*!< Subscription for registration topic. */ diff --git a/src/Wippersnapper_Registration.cpp b/src/Wippersnapper_Registration.cpp deleted file mode 100644 index b5726d748..000000000 --- a/src/Wippersnapper_Registration.cpp +++ /dev/null @@ -1,225 +0,0 @@ -/*! - * @file Wippersnapper_Registration.cpp - * - * This file provides an API for registering a - * device with the Wippersnapper MQTT broker. - * - * Adafruit invests time and resources providing this open source code, - * please support Adafruit and open-source hardware by purchasing - * products from Adafruit! - * - * Copyright (c) Brent Rubell 2020-2021 for Adafruit Industries. - * - * BSD license, all text here must be included in any redistribution. - * - */ - -#include "Wippersnapper_Registration.h" - -/**************************************************************************/ -/*! - @brief Creates a new instance of a registration object. - @param *ws - Reference to Wippersnapper. -*/ -/**************************************************************************/ -Wippersnapper_Registration::Wippersnapper_Registration() { - // wippersnapper_description_v1_CreateDescriptionRequest _message = - // wippersnapper_description_v1_CreateDescriptionRequest_init_zero; -} - -/************************************************************/ -/*! - @brief Registration destructor -*/ -/************************************************************/ -Wippersnapper_Registration::~Wippersnapper_Registration() { - if (_message_buffer) - free(_message_buffer); - - if (_machine_name) - free((char *)_machine_name); - - if (_uid) - _uid = 0; - - // reset FSM - _state = FSMReg::REG_CREATE_ENCODE_MSG; -} - -/************************************************************/ -/*! - @brief Registration State Machine. Handles the - hardware registration process. - @returns True if registered with Wippersnapper - successfully, False otherwise. -*/ -/************************************************************/ -bool Wippersnapper_Registration::processRegistration() { - bool is_registered = false; - - while (!is_registered) { - switch (_state) { - case FSMReg::REG_CREATE_ENCODE_MSG: - WS_DEBUG_PRINT("Encoding registration message..."); - encodeRegMsg(); - _state = FSMReg::REG_PUBLISH_MSG; - break; - case FSMReg::REG_PUBLISH_MSG: - WS_DEBUG_PRINT("Publishing registration message..."); - publishRegMsg(); - _state = FSMReg::REG_DECODE_MSG; - break; - case FSMReg::REG_DECODE_MSG: - if (!pollRegMsg()) { - // delay 1s between polling attempts - delay(1 * 1000); - // attempt to publish again - _state = FSMReg::REG_PUBLISH_MSG; - break; - } - _state = FSMReg::REG_DECODED_MSG; - break; - case FSMReg::REG_DECODED_MSG: - is_registered = true; // if successful - break; - default: - break; - } - } - - return is_registered; -} - -/************************************************************/ -/*! - @brief Creates and encodes a registration protobuf - message. -*/ -/************************************************************/ -void Wippersnapper_Registration::encodeRegMsg() { - _status = true; - - // Create message object - wippersnapper_description_v1_CreateDescriptionRequest _message = - wippersnapper_description_v1_CreateDescriptionRequest_init_zero; - // Fill registration message // - // Set UID - _machine_name = WS._boardId; - _uid = atoi(WS.sUID); - // Set machine_name - strcpy(_message.machine_name, _machine_name); - _message.mac_addr = _uid; - // Set version - strcpy(_message.str_version, WS_VERSION); - - // encode message - pb_ostream_t _msg_stream = - pb_ostream_from_buffer(_message_buffer, sizeof(_message_buffer)); - - _status = pb_encode( - &_msg_stream, - wippersnapper_description_v1_CreateDescriptionRequest_fields, &_message); - _message_len = _msg_stream.bytes_written; - - // verify message - if (!_status) { - // We can't go further here, reset the hardware and try again - WS.haltError("ERROR [CRITICAL]: encoding description message failed!"); - } - WS_DEBUG_PRINTLN("Encoded!"); -} - -/************************************************************/ -/*! - @brief Publishes a registration message to the - Wippersnapper broker. -*/ -/************************************************************/ -void Wippersnapper_Registration::publishRegMsg() { - // Run the network fsm - // WS.runNetFSM(); - // Publish - WS.publish(WS._topic_description, _message_buffer, _message_len, 1); - WS_DEBUG_PRINTLN("Published!") - WS._boardStatus = WS_BOARD_DEF_SENT; -} - -/************************************************************/ -/*! - @brief Polls the broker for a response to a published - registration message. - @returns True if board definition received by broker - successfully, False otherwise. -*/ -/************************************************************/ -bool Wippersnapper_Registration::pollRegMsg() { - bool is_success = false; - // Check network - // WS.runNetFSM(); - - // poll for response from broker - WS.feedWDT(); // let us drop out if we can't process - WS._mqtt->processPackets(100); - if (WS._boardStatus == WS_BOARD_DEF_OK) - is_success = true; - - return is_success; -} - -/************************************************************/ -/*! - @brief Decodes registration message from broker. - @param data - The registration protobuf msg data. - @param len - The length of the protobuf msg data. -*/ -/************************************************************/ -void Wippersnapper_Registration::decodeRegMsg(char *data, uint16_t len) { - WS_DEBUG_PRINTLN("-> Registration Message"); - uint8_t buffer[len]; - memcpy(buffer, data, len); - - // init. CreateDescriptionResponse message - wippersnapper_description_v1_CreateDescriptionResponse message = - wippersnapper_description_v1_CreateDescriptionResponse_init_zero; - - // create input stream for buffer - pb_istream_t stream = pb_istream_from_buffer(buffer, len); - // decode the stream - if (!pb_decode(&stream, - wippersnapper_description_v1_CreateDescriptionResponse_fields, - &message)) { - WS_DEBUG_PRINTLN("Error decoding description status message!"); - } else { // set board status - switch (message.response) { - case wippersnapper_description_v1_CreateDescriptionResponse_Response_RESPONSE_OK: - WS_DEBUG_PRINTLN("Found hardware with:") - WS_DEBUG_PRINT("GPIO Pins: "); - WS_DEBUG_PRINTLN(message.total_gpio_pins); - WS_DEBUG_PRINT("Analog Pins: "); - WS_DEBUG_PRINTLN(message.total_analog_pins); - WS_DEBUG_PRINT("Reference voltage: "); - WS_DEBUG_PRINT(message.reference_voltage); - WS_DEBUG_PRINTLN("v"); - // Initialize Digital IO class - WS._digitalGPIO = new Wippersnapper_DigitalGPIO(message.total_gpio_pins); - // Initialize Analog IO class - WS._analogIO = new Wippersnapper_AnalogIO(message.total_analog_pins, - message.reference_voltage); - WS._boardStatus = WS_BOARD_DEF_OK; - break; - case wippersnapper_description_v1_CreateDescriptionResponse_Response_RESPONSE_BOARD_NOT_FOUND: - WS._boardStatus = WS_BOARD_DEF_INVALID; - break; - case wippersnapper_description_v1_CreateDescriptionResponse_Response_RESPONSE_UNSPECIFIED: - WS._boardStatus = WS_BOARD_DEF_UNSPECIFIED; - break; - default: - WS._boardStatus = WS_BOARD_DEF_UNSPECIFIED; - } - } - - WS_DEBUG_PRINTLN("\nSuccessfully checked in, waiting for commands..."); -} \ No newline at end of file diff --git a/src/Wippersnapper_Registration.h b/src/Wippersnapper_Registration.h deleted file mode 100644 index f52668038..000000000 --- a/src/Wippersnapper_Registration.h +++ /dev/null @@ -1,60 +0,0 @@ -/*! - * @file Wippersnapper_Registration.h - * - * Adafruit invests time and resources providing this open source code, - * please support Adafruit and open-source hardware by purchasing - * products from Adafruit! - * - * Copyright (c) Brent Rubell 2020-2021 for Adafruit Industries. - * - * BSD license, all text here must be included in any redistribution. - * - */ -#ifndef WIPPERSNAPPER_REGISTRATION_H -#define WIPPERSNAPPER_REGISTRATION_H - -#include "Wippersnapper.h" - -// forward decl. -class Wippersnapper; - -/**************************************************************************/ -/*! - @brief Class that negotiates the device's registration process with - the Adafruit IO MQTT broker. -*/ -/**************************************************************************/ -class Wippersnapper_Registration { -public: - Wippersnapper_Registration(); - ~Wippersnapper_Registration(); - - bool processRegistration(); - void encodeRegMsg(); - void publishRegMsg(); - bool pollRegMsg(); - void decodeRegMsg(char *data, uint16_t len); - -private: - enum class FSMReg { - REG_CREATE_ENCODE_MSG, - REG_PUBLISH_MSG, - REG_DECODE_MSG, - REG_DECODED_MSG, - }; - FSMReg _state = FSMReg::REG_CREATE_ENCODE_MSG; - - uint8_t _message_buffer[128]; - size_t _message_len; - bool _status; - - pb_ostream_t _msg_stream; - - // Description message contents - const char *_machine_name; - int32_t _uid; -}; - -extern Wippersnapper WS; - -#endif // WIPPERSNAPPER_REGISTRATION_H \ No newline at end of file diff --git a/src/components/register/Wippersnapper_Register.cpp b/src/components/register/Wippersnapper_Register.cpp new file mode 100644 index 000000000..a9b923faf --- /dev/null +++ b/src/components/register/Wippersnapper_Register.cpp @@ -0,0 +1,162 @@ +/*! + * @file Wippersnapper_Register.cpp + * + * API for registering hardware with the Adafruit.io + * WipperSnapper broker. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2021 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "Wippersnapper.h" + +extern Wippersnapper WS; + +/****************************************************************************/ +/*! + @brief Encodes hardware registration request message and publishes + the message to the Adafruit IO broker. + @returns True if encoded and/or published successfully, False otherwise. +*/ +/****************************************************************************/ +bool Wippersnapper::encodePubRegistrationReq() { + bool _status; + + WS_DEBUG_PRINT("Encoding registration msg..."); + // Create message object + wippersnapper_description_v1_CreateDescriptionRequest _message = + wippersnapper_description_v1_CreateDescriptionRequest_init_zero; + + // Set machine_name + strcpy(_message.machine_name, WS._boardId); + + // Set MAC address + _message.mac_addr = atoi(WS.sUID); + + // Set version + strcpy(_message.str_version, WS_VERSION); + + // encode registration request message + uint8_t _message_buffer[128]; + pb_ostream_t _msg_stream = + pb_ostream_from_buffer(_message_buffer, sizeof(_message_buffer)); + + _status = pb_encode( + &_msg_stream, + wippersnapper_description_v1_CreateDescriptionRequest_fields, &_message); + size_t _message_len = _msg_stream.bytes_written; + + // verify message + if (!_status) + return _status; + + // pubish message + WS.publish(WS._topic_description, _message_buffer, _message_len, 1); + WS_DEBUG_PRINTLN("Published!"); + WS._boardStatus = WS_BOARD_DEF_SENT; + + return _status; +} + +/****************************************************************************/ +/*! + @brief Polls the broker for the hardware registration response message. + + NOTE: This function is BLOCKING and will trigger a WDT reset + if the message has not arrived. + + NOTE: The registration response msg will arrive + async. at the `cbRegistrationStatus` function + and set the `boardStatus` +*/ +/****************************************************************************/ +void Wippersnapper::pollRegistrationResp() { + // Blocking loop, WDT reset upon failure. + while (WS._boardStatus != WS_BOARD_DEF_OK) { + WS_DEBUG_PRINT("Polling for registration message response..."); + WS_DEBUG_PRINTLN(WS._boardStatus); + WS._mqtt->processPackets(10); // poll + // WS._mqtt->ping(); // keepalive + } +} + +/****************************************************************************/ +/*! + @brief Decodes hardware registration response message from the + Adafruit IO MQTT broker and initializes hardware components. + @param data + MQTT message from the Adafruit IO MQTT broker. + @param len + Length of data from the Adafruit IO MQTT broker. +*/ +/****************************************************************************/ +void Wippersnapper::decodeRegistrationResp(char *data, uint16_t len) { + WS_DEBUG_PRINTLN("GOT Registration Response Message:"); + uint8_t buffer[len]; + memcpy(buffer, data, len); + + // init. CreateDescriptionResponse message + wippersnapper_description_v1_CreateDescriptionResponse message = + wippersnapper_description_v1_CreateDescriptionResponse_init_zero; + + // create input stream for buffer + pb_istream_t stream = pb_istream_from_buffer(buffer, len); + // decode the stream + if (!pb_decode(&stream, + wippersnapper_description_v1_CreateDescriptionResponse_fields, + &message)) { + WS.haltError("Could not decode registration response"); + } + // Decode registration response message + if (message.response == + wippersnapper_description_v1_CreateDescriptionResponse_Response_RESPONSE_OK) { + WS_DEBUG_PRINTLN("Hardware Response Msg:") + WS_DEBUG_PRINT("\tGPIO Pins: "); + WS_DEBUG_PRINTLN(message.total_gpio_pins); + WS_DEBUG_PRINT("\tAnalog Pins: "); + WS_DEBUG_PRINTLN(message.total_analog_pins); + WS_DEBUG_PRINT("\tReference voltage: "); + WS_DEBUG_PRINT(message.reference_voltage); + WS_DEBUG_PRINTLN("v"); + // Initialize Digital IO class + WS._digitalGPIO = new Wippersnapper_DigitalGPIO(message.total_gpio_pins); + // Initialize Analog IO class + WS._analogIO = new Wippersnapper_AnalogIO(message.total_analog_pins, + message.reference_voltage); + WS._boardStatus = WS_BOARD_DEF_OK; + + // Publish RegistrationComplete message to broker + wippersnapper_description_v1_RegistrationComplete msg = + wippersnapper_description_v1_RegistrationComplete_init_zero; + msg.is_complete = true; + + // encode registration request message + uint8_t _message_buffer[128]; + pb_ostream_t _msg_stream = + pb_ostream_from_buffer(_message_buffer, sizeof(_message_buffer)); + + bool _status = pb_encode( + &_msg_stream, wippersnapper_description_v1_RegistrationComplete_fields, + &msg); + size_t _message_len = _msg_stream.bytes_written; + + // verify message encoded correctly + if (!_status) { + WS._boardStatus = WS_BOARD_DEF_INVALID; + return; + } + + // Publish message + WS.publish(_topic_description_status_complete, _message_buffer, + _message_len, 1); + WS_DEBUG_PRINTLN("Completed registration process, configuration next!"); + + } else { + WS._boardStatus = WS_BOARD_DEF_INVALID; + } +} \ No newline at end of file