// Parses the input buffer and copies the received payload into the "received payload" buffer // when a "mac rx" message has been received. It is called internally by macTransmit(). // Returns 0 (NoError) or otherwise one of the MacTransmitErrorCodes. uint8_t Sodaq_RN2483::onMacRX() { debugPrintLn("[onMacRX]"); // parse inputbuffer, put payload into packet buffer char* token = strtok(this->inputBuffer, " "); // sanity check if (strcmp(token, STR_RESULT_MAC_RX) != 0) { debugPrintLn("[onMacRX]: mac_rx keyword not found!"); return InternalError; } // port token = strtok(NULL, " "); // payload token = strtok(NULL, " "); // until end of string uint16_t len = strlen(token) + 1; // include termination char memcpy(this->receivedPayloadBuffer, token, len <= this->receivedPayloadBufferSize ? len : this->receivedPayloadBufferSize); this->packetReceived = true; // enable receive() again return NoError; }
// Returns the enum that is mapped to the given "error" message. uint8_t Sodaq_RN2483::lookupMacTransmitError(const char* error) { debugPrint("[lookupMacTransmitError]: "); debugPrintLn(error); if (error[0] == 0) { debugPrintLn("[lookupMacTransmitError]: The string is empty!"); return NoResponse; } StringEnumPair_t errorTable[] = { { STR_RESULT_INVALID_PARAM, InternalError }, { STR_RESULT_NOT_JOINED, NotConnected }, { STR_RESULT_NO_FREE_CHANNEL, Busy }, { STR_RESULT_SILENT, Busy }, { STR_RESULT_FRAME_COUNTER_ERROR, NetworkFatalError }, { STR_RESULT_BUSY, Busy }, { STR_RESULT_MAC_PAUSED, InternalError }, { STR_RESULT_INVALID_DATA_LEN, PayloadSizeError }, { STR_RESULT_MAC_ERROR, NoAcknowledgment }, }; for (StringEnumPair_t * p = errorTable; p->stringValue != NULL; ++p) { if (strcmp(p->stringValue, error) == 0) { debugPrint("[lookupMacTransmitError]: found "); debugPrintLn(p->enumValue); return p->enumValue; } } debugPrintLn("[lookupMacTransmitError]: not found!"); return NoResponse; }
// Sends a reset command to the module and waits for the success response (or timeout). // Returns true on success. bool Sodaq_RN2483::resetDevice() { debugPrintLn("[resetDevice]"); this->loraStream->print(STR_CMD_RESET); this->loraStream->print(CRLF); if (expectString(STR_DEVICE_TYPE_RN)) { if (strstr(this->inputBuffer, STR_DEVICE_TYPE_RN2483) != NULL) { debugPrintLn("The device type is RN2483"); return true; } else if (strstr(this->inputBuffer, STR_DEVICE_TYPE_RN2903) != NULL) { debugPrintLn("The device type is RN2903"); // TODO move into init once it is decided how to handle RN2903-specific operations setFsbChannels(DEFAULT_FSB); return true; } else { debugPrintLn("Unknown device type!"); return false; } } return false; }
// initialize the Lora stack bool Hal::initLora() { #ifdef SODAQ_ONE // enable power, only for the Sodaq One pinMode(ENABLE_PIN_IO, OUTPUT); digitalWrite(ENABLE_PIN_IO, HIGH); // enable power to the grove shield pinMode(11, OUTPUT); digitalWrite(11, HIGH); pinMode(LED_RED, OUTPUT); pinMode(LED_GREEN, OUTPUT); pinMode(LED_BLUE, OUTPUT); GREEN(); #endif loraSerial.begin(LoRaBee.getDefaultBaudRate()); LoRaBee.setDiag(debugSerial); // optional if (LoRaBee.initABP(loraSerial, devAddr, appSKey, nwkSKey, false)) { debugPrintLn("Connection to the network was successful."); isHalInitialized = true; // LoRaBee.resetUplinkCntr(); } else { debugPrintLn("Connection to the network failed!"); } }
// GPIO interface // Sets the mode of a GPIO ( digital in= 0, digital out = 1, analog = 2 ) // Returns true on successful setup. uint8_t Sodaq_RN2483::pinMode(uint8_t gpio, pinModes mode) { //sanitize inputs, //GPIO4 does not do analog debugPrintLn("setting GPIO pin"); if (mode == analog) { if (gpio==4) { return false; debugPrintLn("GPIO4 does not support analog"); } } // only handle GPIO0-13 if (gpio >13) { return false; debugPrintLn("GPIO out of bounds : 0-13 only"); } // sys set pinmode <pinname> <pinFunc> this->loraStream->print(STR_SYS_SET); this->loraStream->print(STR_GPIO_MODE); this->loraStream->print(STR_GPIO); this->loraStream->print(gpio); if (mode == digitalIn) { this->loraStream->print(STR_GPIO_MODE_DIGIN); } else if (mode == digitalOut){ this->loraStream->print(STR_GPIO_MODE_DIGOUT); } else { this->loraStream->print(STR_GPIO_MODE_ANA); } this->loraStream->print(CRLF); // parse response return expectOK(); }
// Sends the given payload with acknowledgement. // Returns 0 (NoError) when transmission is successful or one of the MacTransmitErrorCodes otherwise. uint8_t Sodaq_RN2483::sendReqAck(uint8_t port, const uint8_t* payload, uint8_t size, uint8_t maxRetries) { debugPrintLn("[sendReqAck]"); if (!setMacParam(STR_RETRIES, maxRetries)) { // not a fatal error -just show a debug message debugPrintLn("[sendReqAck] Non-fatal error: setting number of retries failed."); } return macTransmit(STR_CONFIRMED, port, payload, size); }
bool Hal::initHal() { debugPrintLn("Start init"); // initialize all the hardware initLora(); initSwitch(); initLTC(); compass.init(); compass.enableDefault(); htu.begin(); // setAckOn(); debugPrintLn("Einde init"); }
bool TheThingsUno::waitForOK(int waitTime) { String line = readLine(waitTime); if (!line) { debugPrintLn("Wait for OK time-out expired"); return false; } if (line != "ok") { debugPrintLn("Response is not OK: " + line); return false; } return true; }
bool TheThingsUno::join(const byte appEui[8], const byte appKey[16]) { // TODO: Check that mac deveui is set to sys hweui by default, otherwise put that in here sendCommand("mac set appeui", appEui, 8); sendCommand("mac set appkey", appKey, 16); sendCommand("mac join otaa"); if (readLine() != "accepted") { debugPrintLn("Join not accepted"); return false; } debugPrintLn("Join accepted. Status: " + readValue("mac get status")); return true; }
bool TheThingsUno::personalize(const byte devAddr[4], const byte nwkSKey[16], const byte appSKey[16]) { sendCommand("mac set devaddr", devAddr, 4); sendCommand("mac set nwkskey", nwkSKey, 16); sendCommand("mac set appskey", appSKey, 16); sendCommand("mac join abp"); if (readLine() != "accepted") { debugPrintLn("Personalize not accepted"); return false; } debugPrintLn("Personalize accepted. Status: " + readValue("mac get status")); return true; }
bool TheThingsUno::waitForOK(int waitTime, String okMessage) { String line = readLine(waitTime); if (line == "") { debugPrintLn("Wait for OK time-out expired"); return false; } if (line != okMessage) { debugPrintLn("Response is not OK"); return false; } return true; }
String TheThingsUno::readValue(String cmd) { debugPrintLn("Reading value " + cmd); modemStream->println(cmd); return readLine(); }
void Sodaq_RN2483::sleep() { debugPrintLn("[sleep]"); this->loraStream->print(STR_CMD_SLEEP); this->loraStream->print(CRLF); }
bool TheThingsUno::sendCommand(String cmd, int waitTime) { debugPrintLn("Sending: " + cmd); modemStream->println(cmd); return waitForOK(waitTime); }
// Waits for the given string. Returns true if the string is received before a timeout. // Returns false if a timeout occurs or if another string is received. bool Sodaq_RN2483::expectString(const char* str, uint16_t timeout) { debugPrint("[expectString] expecting "); debugPrint(str); unsigned long end = millis() + timeout; while (millis() < end) { debugPrint("."); uint16_t len = readLn(); if (len > 0) { debugPrint("("); debugWrite((uint8_t*)this->inputBuffer, len); debugPrint(")"); if (strstr(this->inputBuffer, str) != NULL) { debugPrintLn(" found a match!"); return true; } //return false; } } return false; }
// Sends a reset command to the module and waits for the success response (or timeout). // Returns true on success. bool Sodaq_RN2483::resetDevice() { debugPrintLn("[resetDevice]"); this->loraStream->print(STR_CMD_RESET); this->loraStream->print(CRLF); return expectString(STR_DEVICE_TYPE); }
bool TheThingsUno::sendCommand(String cmd, String value, int waitTime) { debugPrintLn("Sending: " + cmd + " with value " + value); modemStream->print(cmd + " "); for (int i = 0; i < value.length(); i++) modemStream->print(String(value.charAt(i), HEX)); modemStream->println(); return waitForOK(waitTime); }
bool TheThingsUno::sendCommand(String cmd, const byte* buffer, int length, int waitTime) { debugPrintLn("Sending: " + cmd + " with " + String(length) + " bytes"); modemStream->print(cmd + " "); for (int i = 0; i < length; i++) modemStream->print(String(buffer[i], HEX)); modemStream->println(); return waitForOK(waitTime); }
// Sends a join network command to the device and waits for the response (or timeout). // Returns true on success. bool Sodaq_RN2483::joinNetwork(const char* type) { debugPrintLn("[joinNetwork]"); this->loraStream->print(STR_CMD_JOIN); this->loraStream->print(type); this->loraStream->print(CRLF); return expectOK() && expectString(STR_ACCEPTED, 10000); }
String TheThingsUno::readLine(int waitTime) { unsigned long start = millis(); while (millis() < start + waitTime) { String line = modemStream->readStringUntil('\n'); if (line.length() >= 0) { debugPrintLn("Read " + line); return line.substring(0, line.length() - 1); // TODO: Check this } } return ""; }
bool Sodaq_RN2483::getMacParam(const char* paramName, char* paramValue, uint8_t size) { debugPrint("[getMacParam] "); debugPrint(paramName); debugPrint("? "); this->loraStream->print(STR_CMD_GET); this->loraStream->print(paramName); this->loraStream->print(CRLF); if (readLn() > 0) { strncpy (paramValue, this->inputBuffer, size-1); paramValue[size-1] = 0x00; debugPrintLn(paramValue); return true; } debugPrintLn(" ERROR!"); return false; }
/*! * \brief Publish a message * \param topic The topic of the publish * \param msg The (binary) message to publish * \param msg_len The length of the (binary) message * * Create a PUBLISH packet and send it to the MQTT server * * \returns false if sending the message failed somehow */ bool MQTT::publish(const char * topic, const uint8_t * msg, size_t msg_len, uint8_t qos) { debugPrintLn(DEBUG_PREFIX + "PUBLISH topic: " + topic); debugPrintLn(DEBUG_PREFIX + "PUBLISH msg:"); debugDump(msg, msg_len); bool retval = false; if (_transport == 0) { goto ending; } if (_state != ST_MQTT_CONNECTED) { if (!connect()) { goto ending; } } newPacketIdentifier(); uint8_t pckt[MQTT_MAX_PACKET_LENGTH]; size_t pckt_len; // Assemble the PUBLISH packet pckt_len = assemblePublishPacket(pckt, sizeof(pckt), topic, msg, msg_len, qos); if (pckt_len == 0 || !_transport->sendMQTTPacket(pckt, pckt_len)) { goto ending; } if (qos == 0) { // Nothing to be received } else if (qos == 1) { // Handle incoming PUBACK } else if (qos == 2) { // Handle incoming PUBREC } else { // Shouldn't happen } retval = true; ending: return retval; }
// Sets the digital state of a pin // Returns true on successful setting. uint8_t Sodaq_RN2483::digitalWrite(uint8_t gpio, uint8_t state) { //sanitize inputs, // only handle GPIO0-13 debugPrintLn("digitalWrite"); if (gpio >13) { return false; debugPrintLn("GPIO out of bounds : 0-13 only"); } // sys set pindig <pinname> <pinstate> // example: sys set pindig GPIO5 1 this->loraStream->print(STR_SYS_SET); this->loraStream->print(STR_GPIO_DIG); this->loraStream->print(STR_GPIO); this->loraStream->print(gpio); this->loraStream->print(" "); this->loraStream->print(state); this->loraStream->print(CRLF); // parse response return expectOK(); }
// Gets the digital state of a pin // Returns the state of a GPIO ( true or false ), returns false if pin not set up uint8_t Sodaq_RN2483::digitalRead(uint8_t gpio) { //sanitize inputs, // only handle GPIO0-13 debugPrintLn("digitalRead"); if (gpio >13) { return false; debugPrintLn("GPIO out of bounds : 0-13 only"); } // sys get pindig <pinname> this->loraStream->print(STR_SYS_GET); this->loraStream->print(STR_GPIO_DIG); this->loraStream->print(STR_GPIO); this->loraStream->print(gpio); this->loraStream->print(CRLF); // parse response // "1" means pin is High, so return true // will return false / 0 otherwise return expectString("1"); }
// Copies the latest received packet (optionally starting from the "payloadStartPosition" // position of the payload) into the given "buffer", up to "size" number of bytes. // Returns the number of bytes written or 0 if no packet is received since last transmission. uint16_t Sodaq_RN2483::receive(uint8_t* buffer, uint16_t size, uint16_t payloadStartPosition) { debugPrintLn("[receive]"); if (!this->packetReceived) { debugPrintLn("[receive]: There is no packet received!"); return 0; } uint16_t inputIndex = payloadStartPosition * 2; // payloadStartPosition is in bytes, not hex char pairs uint16_t outputIndex = 0; // check that the asked starting position is within bounds if (inputIndex >= this->receivedPayloadBufferSize) { debugPrintLn("[receive]: Out of bounds start position!"); return 0; } // stop at the first string termination char, or if output buffer is over, or if payload buffer is over while (outputIndex < size && inputIndex + 1 < this->receivedPayloadBufferSize && this->receivedPayloadBuffer[inputIndex] != 0 && this->receivedPayloadBuffer[inputIndex + 1] != 0) { buffer[outputIndex] = HEX_PAIR_TO_BYTE( this->receivedPayloadBuffer[inputIndex], this->receivedPayloadBuffer[inputIndex + 1]); inputIndex += 2; outputIndex++; } // Note: if the payload has an odd length, the last char is discarded buffer[outputIndex] = 0; // terminate the string debugPrintLn("[receive]: Done"); return outputIndex; }
// Initializes the device and connects to the network using Activation By Personalization. // Returns true on successful connection. bool Sodaq_RN2483::initABP(Stream& stream, const uint8_t devAddr[4], const uint8_t appSKey[16], const uint8_t nwkSKey[16]) { debugPrintLn("[initABP]"); init(stream); return resetDevice() && setMacParam(STR_DEV_ADDR, devAddr, 4) && setMacParam(STR_APP_SESSION_KEY, appSKey, 16) && setMacParam(STR_NETWORK_SESSION_KEY, nwkSKey, 16) && //setMacParam(STR_ADR, BOOL_TO_ONOFF(adr)) && joinNetwork(STR_ABP); }
// Initializes the device and connects to the network using Over-The-Air Activation. // Returns true on successful connection. bool Sodaq_RN2483::initOTA(Stream& stream, const uint8_t devEUI[8], const uint8_t appEUI[8], const uint8_t appKey[16]) { debugPrintLn("[initOTA]"); init(stream); return resetDevice() && setMacParam(STR_DEV_EUI, devEUI, 8) && setMacParam(STR_APP_EUI, appEUI, 8) && setMacParam(STR_APP_KEY, appKey, 16) && // setMacParam(STR_ADR, BOOL_TO_ONOFF(adr)) && joinNetwork(STR_OTAA); }
/*! * \brief do stuff * * This function is used to do the following: * - read incoming packets and call handlers (if there is one) */ bool MQTT::loop() { // Is there a packet? bool status; size_t pckt_size; pckt_size = _transport->availableMQTTPacket(); if (pckt_size > 0) { uint8_t mqtt_packet[256]; char topic[128]; uint8_t msg[128]; MQTTPacketInfo pckt_info; pckt_info._topic = topic; pckt_info._topic_size = sizeof(topic); pckt_info._msg = msg; pckt_info._msg_size = sizeof(msg); pckt_size = _transport->receiveMQTTPacket(mqtt_packet, sizeof(mqtt_packet)); if (pckt_size > 0) { // TODO debugPrintLn(DEBUG_PREFIX + " received packet:"); debugDump(mqtt_packet, pckt_size); switch ((mqtt_packet[0] >> 4) & 0xF) { case CPT_PUBLISH: status = dissectPublishPacket(mqtt_packet, pckt_size, pckt_info); if (status) { if (pckt_info._qos == 0) { // Nothing else to do } else if (pckt_info._qos == 1) { // TODO // Send PUBACK } else if (pckt_info._qos == 2) { // TODO // Send PUBREC } else { // Shouldn't happen } if (_publishHandler) { _publishHandler(topic, msg, pckt_info._msg_truncated_length); } } break; default: if (_packetHandler) { _packetHandler(mqtt_packet, pckt_size); } break; } }
// Gets the analog value of a pin // Returns the ADC reading ( 0-1023 ) uint16_t Sodaq_RN2483::analogRead(uint8_t gpio) { uint16_t analogReading = 0; //sanitize inputs, // only handle GPIO0-13, but not 4 debugPrintLn("analogRead"); if (gpio >13 && gpio != 4) { return 0; debugPrintLn("GPIO out of bounds : 0-13 only, and not 4"); } // sys get pinana <pinname> // example : sys get pinana GPIO0 this->loraStream->print(STR_SYS_GET); this->loraStream->print(STR_GPIO_ANA); this->loraStream->print(STR_GPIO); this->loraStream->print(gpio); this->loraStream->print(CRLF); // parse response // "1" means pin is High, so return true // will return false otherwise analogReading = this->loraStream->parseInt(); return analogReading; }
// Sends the given mac command together with the given paramValue // to the device and awaits for the response. // Returns true on success. // NOTE: paramName should include a trailing space bool Sodaq_RN2483::setMacParam(const char* paramName, const char* paramValue) { debugPrint("[setMacParam] "); debugPrint(paramName); debugPrint("= "); debugPrintLn(paramValue); this->loraStream->print(STR_CMD_SET); this->loraStream->print(paramName); this->loraStream->print(paramValue); this->loraStream->print(CRLF); return expectOK(); }