diff --git a/docs/knownlimitations.md b/docs/knownlimitations.md index 5f55ac89..d7957b4b 100644 --- a/docs/knownlimitations.md +++ b/docs/knownlimitations.md @@ -7,6 +7,9 @@ such sensors and known workarounds if they exist. #### Grove Sensors + * **RN2903** Click 2 version. This device will not work using the + Edison UART on the Arduino breakout. It does work on Edison using a + USB->serial interface. * **Grove LCD RGB Backlit** (JHD1313M1) requires 5V and should be used with an external power supply connected to the board to function properly. Although some high powered USB ports might be enough, in most cases you will encounter diff --git a/examples/c++/rn2903-p2p-rx.cxx b/examples/c++/rn2903-p2p-rx.cxx new file mode 100644 index 00000000..5a0970d6 --- /dev/null +++ b/examples/c++/rn2903-p2p-rx.cxx @@ -0,0 +1,122 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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 +#include +#include +#include +#include + +#include "rn2903.hpp" +#include "upm_utilities.h" + +using namespace std; + +bool shouldRun = true; + +void sig_handler(int signo) +{ + if (signo == SIGINT) + shouldRun = false; +} + + +int main(int argc, char **argv) +{ + signal(SIGINT, sig_handler); +//! [Interesting] + + string defaultDev = "/dev/ttyUSB0"; + if (argc > 1) + defaultDev = argv[1]; + + cout << "Using device: " << defaultDev << endl; + + // Instantiate a RN2903 sensor on defaultDev at 57600 baud. + upm::RN2903 sensor = upm::RN2903(defaultDev, + RN2903_DEFAULT_BAUDRATE); + + // To use an internal UART understood by MRAA, use the following + // to inititialize rather than the above, which by default uses a + // tty path. + // + // upm::RN2903 sensor = upm::RN2903(0, RN2903_DEFAULT_BAUDRATE); + + // enable debugging + // sensor.setDebug(true); + + // get version + if (sensor.command("sys get ver")) + { + cout << "Failed to retrieve device version string" << endl; + return 1; + } + cout << "Firmware version: " << sensor.getResponse() << endl; + + cout << "Hardware EUI: " << sensor.getHardwareEUI() << endl; + + // For this example, we will just try to receive a packet + // transmitted by the p2p-tx rn2903 example. We reset the + // device to defaults, and we do not make any adjustments to the + // radio configuration. You will probably want to do so for a + // real life application. + + // The first thing to do is to suspend the LoRaWAN stack on the device. + sensor.macPause(); + + // We will use continuous mode (window_size 0), though the default + // radio watch dog timer will expire every 15 seconds. We will + // just loop here. + + while (shouldRun) + { + cout << "Waiting for packet..." << endl; + RN2903_RESPONSE_T rv; + rv = sensor.radioRx(0); + if (rv) + { + cout << "radioRx() failed with code " << int(rv) << endl; + } + else + { + string resp = sensor.getResponse(); + string payload = sensor.getRadioRxPayload(); + if (!payload.size()) + cout << "Got response: '" << resp << "'" << endl; + else + cout <<"Got payload: '" + << sensor.fromHex(payload) + << "'" + << endl; + } + + cout << endl; + } + + cout << "Exiting" << endl; + +//! [Interesting] + + return 0; +} diff --git a/examples/c++/rn2903-p2p-tx.cxx b/examples/c++/rn2903-p2p-tx.cxx new file mode 100644 index 00000000..3e5929fa --- /dev/null +++ b/examples/c++/rn2903-p2p-tx.cxx @@ -0,0 +1,126 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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 +#include +#include +#include +#include + +#include "rn2903.hpp" +#include "upm_utilities.h" + +using namespace std; + +bool shouldRun = true; + +void sig_handler(int signo) +{ + if (signo == SIGINT) + shouldRun = false; +} + + +int main(int argc, char **argv) +{ + signal(SIGINT, sig_handler); +//! [Interesting] + + string defaultDev = "/dev/ttyUSB0"; + if (argc > 1) + defaultDev = argv[1]; + + cout << "Using device: " << defaultDev << endl; + + // Instantiate a RN2903 sensor on defaultDev at 57600 baud. + upm::RN2903 sensor = upm::RN2903(defaultDev, + RN2903_DEFAULT_BAUDRATE); + + // To use an internal UART understood by MRAA, use the following + // to inititialize rather than the above, which by default uses a + // tty path. + // + // upm::RN2903 sensor = upm::RN2903(0, RN2903_DEFAULT_BAUDRATE); + + // enable debugging + // sensor.setDebug(true); + + // get version + if (sensor.command("sys get ver")) + { + cout << "Failed to retrieve device version string" << endl; + return 1; + } + cout << "Firmware version: " << sensor.getResponse() << endl; + + cout << "Hardware EUI: " << sensor.getHardwareEUI() << endl; + + // For this example, we will just try transmitting a packet over + // LoRa. We reset the device to defaults, and we do not make any + // adjustments to the radio configuration. You will probably want + // to do so for a real life application. + + // The first thing to do is to suspend the LoRaWAN stack on the device. + sensor.macPause(); + + // the default radio watchdog timer is set for 15 seconds, so we + // will send a packet every 10 seconds. In reality, local + // restrictions limit the amount of time on the air, so in a real + // implementation, you would not want to send packets that + // frequently. + + int count = 0; + while (shouldRun) + { + ostringstream output; + output << "Ping " << count++; + + // All payloads must be hex encoded + string payload = sensor.toHex(output.str()); + + cout << "Transmitting a packet, data: '" + << output.str() + << "' -> hex: '" + << payload + << "'" + << endl; + + RN2903_RESPONSE_T rv; + rv = sensor.radioTx(payload); + + if (rv == RN2903_RESPONSE_OK) + cout << "Transmit successful." << endl; + else + cout << "Transmit failed with code " << int(rv) << endl; + + cout << endl; + upm_delay(10); + } + + cout << "Exiting" << endl; + +//! [Interesting] + + return 0; +} diff --git a/examples/c++/rn2903.cxx b/examples/c++/rn2903.cxx new file mode 100644 index 00000000..89e7eaf2 --- /dev/null +++ b/examples/c++/rn2903.cxx @@ -0,0 +1,137 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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 +#include +#include + +#include "rn2903.hpp" +#include "upm_utilities.h" + +using namespace std; + +int main(int argc, char **argv) +{ +//! [Interesting] + + string defaultDev = "/dev/ttyUSB0"; + if (argc > 1) + defaultDev = argv[1]; + + cout << "Using device: " << defaultDev << endl; + + // Instantiate a RN2903 sensor on defaultDev at 57600 baud. + upm::RN2903 sensor = upm::RN2903(defaultDev, + RN2903_DEFAULT_BAUDRATE); + + // To use an internal UART understood by MRAA, use the following + // to inititialize rather than the above, which by default uses a + // tty path. + // + // upm::RN2903 sensor = upm::RN2903(0, RN2903_DEFAULT_BAUDRATE); + + // enable debugging + // sensor.setDebug(true); + + // get version + if (sensor.command("sys get ver")) + { + cout << "Failed to retrieve device version string" << endl; + return 1; + } + cout << "Firmware version: " << sensor.getResponse() << endl; + + cout << "Hardware EUI: " << sensor.getHardwareEUI() << endl; + + // we can support two types of join, OTAA and ABP. Each requires + // that certain parameters be set first. We will only attempt ABP + // joining with this example since it's the only one that can + // succeed without actual configuration. In both cases, if you + // are actually attempting to join a real LoRaWAN network, you + // must change the parameters below to match the network you are + // attempting to join. + + // For OTAA, you need to supply valid Device EUI, Application EUI, + // and Application key: + // + // sensor.setDeviceEUI("0011223344556677"); + // sensor.setApplicationEUI("0011223344556677"); + // sensor.setApplicationKey("01234567012345670123456701234567"); + // + // RN2903_JOIN_STATUS_T rv = sensor.join(RN2903_JOIN_TYPE_OTAA); + // A successful join will return RN2903_JOIN_STATUS_ACCEPTED (0). + // cout << "JOIN: got rv " << int(rv) << endl; + + // Try an ABP join. Note, these parameters are made up. For a + // real network, you will want to use the correct values + // obviously. For an ABP join, you need to supply the Device + // Address, Network Session Key, and the Application Session Key. + + sensor.setDeviceAddr("00112233"); + sensor.setNetworkSessionKey("00112233001122330011223300112233"); + sensor.setApplicationSessionKey("00112233001122330011223300112233"); + + RN2903_JOIN_STATUS_T rv = sensor.join(RN2903_JOIN_TYPE_ABP); + if (rv == RN2903_JOIN_STATUS_ACCEPTED) + { + cout << "Join successful." << endl; + + // All transmit payloads must be hex encoded strings, so + // pretend we have a temperature sensor that gave us a value + // of 25.6 C, and we want to transmit it. + string faketemp = "25.6"; + cout << "Transmitting a packet..." << endl; + + RN2903_MAC_TX_STATUS_T trv; + trv = sensor.macTx(RN2903_MAC_MSG_TYPE_UNCONFIRMED, + 1, // port number + sensor.toHex(faketemp)); + + if (trv == RN2903_MAC_TX_STATUS_TX_OK) + cout << "Transmit successful." << endl; + else + { + // check to see if we got a downlink packet + if (trv == RN2903_MAC_TX_STATUS_RX_RECEIVED) + { + cout << "Transmit successful, downlink packet received: " + << sensor.getResponse(); + } + else + { + cout << "Transmit failed with code " << int(trv) << endl; + } + } + } + else + { + cout << "Join failed with code " << int(rv) << endl; + } + + cout << "Exiting" << endl; + +//! [Interesting] + + return 0; +} diff --git a/examples/c/rn2903-p2p-rx.c b/examples/c/rn2903-p2p-rx.c new file mode 100644 index 00000000..5aa535f1 --- /dev/null +++ b/examples/c/rn2903-p2p-rx.c @@ -0,0 +1,137 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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 +#include +#include +#include + +#include "rn2903.h" +#include "upm_utilities.h" +#include "upm_platform.h" + +int shouldRun = true; + +void sig_handler(int signo) +{ + if (signo == SIGINT) + shouldRun = false; +} + +#if defined(UPM_PLATFORM_ZEPHYR) && !defined(CONFIG_STDOUT_CONSOLE) +# define printf printk +#endif + +int main(int argc, char **argv) +{ +//! [Interesting] + + char *defaultDev = "/dev/ttyUSB0"; + if (argc > 1) + defaultDev = argv[1]; + + printf("Using device: %s\n", defaultDev); + + // Instantiate a RN2903 sensor on defaultDev at 57600 baud. +#if defined(UPM_PLATFORM_ZEPHYR) + rn2903_context sensor = rn2903_init(0, RN2903_DEFAULT_BAUDRATE); +#else + rn2903_context sensor = rn2903_init_tty(defaultDev, + RN2903_DEFAULT_BAUDRATE); +#endif + + // To use an internal UART understood by MRAA, use the following + // to inititialize rather than the above, which by default uses a + // tty path. + // + // rn2903_context sensor = rn2903_init(0, RN2903_DEFAULT_BAUDRATE); + + if (!sensor) + { + printf("rn2903_init_tty() failed.\n"); + return 1; + } + + // enable debugging + // rn2903_set_debug(sensor, true); + + // get version + if (rn2903_command(sensor, "sys get ver")) + { + printf("Failed to retrieve device version string\n"); + rn2903_close(sensor); + return 1; + } + printf("Firmware version: %s\n", rn2903_get_response(sensor)); + + printf("Hardware EUI: %s\n", rn2903_get_hardware_eui(sensor)); + + // For this example, we will just try to receive a packet + // transmitted by the p2p-tx rn2903 example. We reset the + // device to defaults, and we do not make any adjustments to the + // radio configuration. You will probably want to do so for a + // real life application. + + // The first thing to do is to suspend the LoRaWAN stack on the device. + if (rn2903_mac_pause(sensor)) + { + printf("Failed to pause the LoRaWAN stack\n"); + rn2903_close(sensor); + return 1; + } + + // We will use continuous mode (window_size 0), though the default + // radio watch dog timer will expire every 15 seconds. We will + // just loop here. + while (shouldRun) + { + printf("Waiting for packet...\n"); + RN2903_RESPONSE_T rv; + rv = rn2903_radio_rx(sensor, 0); + if (rv) + { + printf("rn2903_radio_rx() failed with code (%d)\n", rv); + } + else + { + const char *resp = rn2903_get_response(sensor); + const char *payload = rn2903_get_radio_rx_payload(sensor); + if (!payload) + printf("Got response: '%s'\n", resp); + else + printf("Got payload: '%s'\n", + rn2903_from_hex(sensor, payload)); + } + + printf("\n"); + } + + printf("Exiting\n"); + + rn2903_close(sensor); + +//! [Interesting] + + return 0; +} diff --git a/examples/c/rn2903-p2p-tx.c b/examples/c/rn2903-p2p-tx.c new file mode 100644 index 00000000..229e7b26 --- /dev/null +++ b/examples/c/rn2903-p2p-tx.c @@ -0,0 +1,139 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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 +#include +#include +#include + +#include "rn2903.h" +#include "upm_utilities.h" +#include "upm_platform.h" + +bool shouldRun = true; + +void sig_handler(int signo) +{ + if (signo == SIGINT) + shouldRun = false; +} + +#if defined(UPM_PLATFORM_ZEPHYR) && !defined(CONFIG_STDOUT_CONSOLE) +# define printf printk +#endif + +int main(int argc, char **argv) +{ +//! [Interesting] + + char *defaultDev = "/dev/ttyUSB0"; + if (argc > 1) + defaultDev = argv[1]; + + printf("Using device: %s\n", defaultDev); + + // Instantiate a RN2903 sensor on defaultDev at 57600 baud. +#if defined(UPM_PLATFORM_ZEPHYR) + rn2903_context sensor = rn2903_init(0, RN2903_DEFAULT_BAUDRATE); +#else + rn2903_context sensor = rn2903_init_tty(defaultDev, + RN2903_DEFAULT_BAUDRATE); +#endif + + // To use an internal UART understood by MRAA, use the following + // to inititialize rather than the above, which by default uses a + // tty path. + // + // rn2903_context sensor = rn2903_init(0, RN2903_DEFAULT_BAUDRATE); + + if (!sensor) + { + printf("rn2903_init_tty() failed.\n"); + return 1; + } + + // enable debugging + // rn2903_set_debug(sensor, true); + + // get version + if (rn2903_command(sensor, "sys get ver")) + { + printf("Failed to retrieve device version string\n"); + rn2903_close(sensor); + return 1; + } + printf("Firmware version: %s\n", rn2903_get_response(sensor)); + + printf("Hardware EUI: %s\n", rn2903_get_hardware_eui(sensor)); + + // For this example, we will just try transmitting a packet over + // LoRa. We reset the device to defaults, and we do not make any + // adjustments to the radio configuration. You will probably want + // to do so for a real life application. + + // The first thing to do is to suspend the LoRaWAN stack on the device. + if (rn2903_mac_pause(sensor)) + { + printf("Failed to pause the LoRaWAN stack\n"); + rn2903_close(sensor); + return 1; + } + + // the default radio watchdog timer is set for 15 seconds, so we + // will send a packet every 10 seconds. In reality, local + // restrictions limit the amount of time on the air, so in a real + // implementation, you would not want to send packets that + // frequently. + + int count = 0; + while (shouldRun) + { + char pingbuf[32] = {}; + snprintf(pingbuf, 32, "Ping %d", count++); + // All payloads must be hex encoded + const char *payload = rn2903_to_hex(sensor, pingbuf, strlen(pingbuf)); + + printf("Transmitting a packet, data: '%s' -> hex: '%s'\n", + pingbuf, payload); + + RN2903_RESPONSE_T rv; + rv = rn2903_radio_tx(sensor, payload); + + if (rv == RN2903_RESPONSE_OK) + printf("Transmit successful.\n"); + else + printf("Transmit failed with code %d.\n", rv); + + printf("\n"); + upm_delay(10); + } + + printf("Exiting\n"); + + rn2903_close(sensor); + +//! [Interesting] + + return 0; +} diff --git a/examples/c/rn2903.c b/examples/c/rn2903.c new file mode 100644 index 00000000..3b0e5502 --- /dev/null +++ b/examples/c/rn2903.c @@ -0,0 +1,153 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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 +#include +#include + +#include "rn2903.h" +#include "upm_utilities.h" +#include "upm_platform.h" + +#if defined(UPM_PLATFORM_ZEPHYR) && !defined(CONFIG_STDOUT_CONSOLE) +# define printf printk +#endif + +int main(int argc, char **argv) +{ +//! [Interesting] + + char *defaultDev = "/dev/ttyUSB0"; + if (argc > 1) + defaultDev = argv[1]; + + printf("Using device: %s\n", defaultDev); + + // Instantiate a RN2903 sensor on defaultDev at 57600 baud. +#if defined(UPM_PLATFORM_ZEPHYR) + rn2903_context sensor = rn2903_init(0, RN2903_DEFAULT_BAUDRATE); +#else + rn2903_context sensor = rn2903_init_tty(defaultDev, + RN2903_DEFAULT_BAUDRATE); +#endif + + // To use an internal UART understood by MRAA, use the following + // to inititialize rather than the above, which by default uses a + // tty path. + // + // rn2903_context sensor = rn2903_init(0, RN2903_DEFAULT_BAUDRATE); + + if (!sensor) + { + printf("rn2903_init_tty() failed.\n"); + return 1; + } + + // enable debugging + // rn2903_set_debug(sensor, true); + + // get version + if (rn2903_command(sensor, "sys get ver")) + { + printf("Failed to retrieve device version string\n"); + rn2903_close(sensor); + return 1; + } + printf("Firmware version: %s\n", rn2903_get_response(sensor)); + + printf("Hardware EUI: %s\n", rn2903_get_hardware_eui(sensor)); + + // we can support two types of join, OTAA and ABP. Each requires + // that certain parameters be set first. We will only attempt ABP + // joining with this example since it's the only one that can + // succeed without actual configuration. In both cases, if you + // are actually attempting to join a real LoRaWAN network, you + // must change the parameters below to match the network you are + // attempting to join. + + // For OTAA, you need to supply valid Device EUI, Application EUI, + // and Application key: + // + // rn2903_set_device_eui(sensor, "0011223344556677"); + // rn2903_set_application_eui(sensor, "0011223344556677"); + // rn2903_set_application_key(sensor, "01234567012345670123456701234567"); + // + // RN2903_JOIN_STATUS_T rv = rn2903_join(sensor, RN2903_JOIN_TYPE_OTAA); + // A successful join will return RN2903_JOIN_STATUS_ACCEPTED (0). + // printf("JOIN: got rv %d\n", rv); + + // Try an ABP join. Note, these parameters are made up. For a + // real network, you will want to use the correct values + // obviously. For an ABP join, you need to supply the Device + // Address, Network Session Key, and the Application Session Key. + + rn2903_set_device_addr(sensor, "00112233"); + rn2903_set_network_session_key(sensor, "00112233001122330011223300112233"); + rn2903_set_application_session_key(sensor, + "00112233001122330011223300112233"); + RN2903_JOIN_STATUS_T rv = rn2903_join(sensor, RN2903_JOIN_TYPE_ABP); + if (rv == RN2903_JOIN_STATUS_ACCEPTED) + { + printf("Join successful.\n"); + + // All transmit payloads must be hex encoded strings, so + // pretend we have a temperature sensor that gave us a value + // of 25.6 C, and we want to transmit it. + const char *faketemp = "25.6"; + printf("Transmitting a packet....\n"); + + RN2903_MAC_TX_STATUS_T trv; + trv = rn2903_mac_tx(sensor, RN2903_MAC_MSG_TYPE_UNCONFIRMED, + 1, // port number + rn2903_to_hex(sensor, faketemp, strlen(faketemp))); + + if (trv == RN2903_MAC_TX_STATUS_TX_OK) + printf("Transmit successful.\n"); + else + { + // check to see if we got a downlink packet + if (trv == RN2903_MAC_TX_STATUS_RX_RECEIVED) + { + printf("Transmit successful, downlink packet received: %s\n", + rn2903_get_response(sensor)); + } + else + { + printf("Transmit failed with code %d.\n", trv); + } + } + } + else + { + printf("Join failed with code %d.\n", rv); + } + + printf("Exiting\n"); + + rn2903_close(sensor); + +//! [Interesting] + + return 0; +} diff --git a/examples/java/CMakeLists.txt b/examples/java/CMakeLists.txt index e915162d..e3f6fd66 100644 --- a/examples/java/CMakeLists.txt +++ b/examples/java/CMakeLists.txt @@ -189,6 +189,7 @@ add_example(BMM150_Example bmm150) add_example(LSM303AGR_Example lsm303agr) add_example(LSM303D_Example lsm303d) add_example(VEML6070Sample veml6070) +add_example(RN2903_Example rn2903) add_example_with_path(Jhd1313m1_lcdSample jhd1313m1 jhd1313m1) add_example_with_path(Jhd1313m1Sample jhd1313m1 jhd1313m1) @@ -215,3 +216,5 @@ add_example_with_path(NMEAGPS_I2C_Example nmea_gps nmea_gps) add_example_with_path(MCP2515_TXRX_Example mcp2515 mcp2515) add_example_with_path(LE910_Example uartat uartat) add_example_with_path(SpeakerPWMSample speaker speaker) +add_example_with_path(RN2903_P2P_RX_Example rn2903 rn2903) +add_example_with_path(RN2903_P2P_TX_Example rn2903 rn2903) diff --git a/examples/java/RN2903_Example.java b/examples/java/RN2903_Example.java new file mode 100644 index 00000000..2d3cb7a9 --- /dev/null +++ b/examples/java/RN2903_Example.java @@ -0,0 +1,138 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 Intel Corporation. + * + * The MIT License + * + * 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. + */ + +import upm_rn2903.*; + +public class RN2903_Example +{ + private static String defaultDev = "/dev/ttyUSB0"; + + public static void main(String[] args) throws InterruptedException + { +// ! [Interesting] + + if (args.length > 0) + defaultDev = args[0]; + + System.out.println("Using device " + defaultDev); + + // Instantiate a RN2903 sensor on defaultDev at 57600 baud. + RN2903 sensor = new RN2903(defaultDev, + javaupm_rn2903.RN2903_DEFAULT_BAUDRATE); + + // To use an internal UART understood by MRAA, use the following + // to inititialize rather than the above, which by default uses a + // tty path. + // + // RN2903 sensor = new RN2903(defaultDev, + // upm_rn2903.javaupm_rn2903.RN2903_DEFAULT_BAUDRATE); + + // enable debugging + // sensor.setDebug(true); + + // get version + if (sensor.command("sys get ver") + != RN2903_RESPONSE_T.RN2903_RESPONSE_OK) + { + System.out.println("Failed to retrieve device version string"); + System.exit(1); + } + System.out.println("Firmware version: " + sensor.getResponse()); + + System.out.println("Hardware EUI: " + sensor.getHardwareEUI()); + + // we can support two types of join, OTAA and ABP. Each requires + // that certain parameters be set first. We will only attempt ABP + // joining with this example since it's the only one that can + // succeed without actual configuration. In both cases, if you + // are actually attempting to join a real LoRaWAN network, you + // must change the parameters below to match the network you are + // attempting to join. + + // For OTAA, you need to supply valid Device EUI, Application EUI, + // and Application key: + // + // sensor.setDeviceEUI("0011223344556677"); + // sensor.setApplicationEUI("0011223344556677"); + // sensor.setApplicationKey("01234567012345670123456701234567"); + // + // RN2903_JOIN_STATUS_T rv = sensor.join(RN2903_JOIN_TYPE_OTAA); + // A successful join will return RN2903_JOIN_STATUS_ACCEPTED (0). + // cout << "JOIN: got rv " << int(rv) << endl; + + // Try an ABP join. Note, these parameters are made up. For a + // real network, you will want to use the correct values + // obviously. For an ABP join, you need to supply the Device + // Address, Network Session Key, and the Application Session Key. + + sensor.setDeviceAddr("00112233"); + sensor.setNetworkSessionKey("00112233001122330011223300112233"); + sensor.setApplicationSessionKey("00112233001122330011223300112233"); + + RN2903_JOIN_STATUS_T rv = + sensor.join(RN2903_JOIN_TYPE_T.RN2903_JOIN_TYPE_ABP); + + if (rv == RN2903_JOIN_STATUS_T.RN2903_JOIN_STATUS_ACCEPTED) + { + System.out.println("Join successful."); + + // All transmit payloads must be hex encoded strings, so + // pretend we have a temperature sensor that gave us a value + // of 25.6 C, and we want to transmit it. + String faketemp = "25.6"; + System.out.println("Transmitting a packet..."); + + RN2903_MAC_TX_STATUS_T trv = + sensor.macTx(RN2903_MAC_MSG_TYPE_T.RN2903_MAC_MSG_TYPE_UNCONFIRMED, + 1, // port number + sensor.toHex(faketemp)); + + if (trv == RN2903_MAC_TX_STATUS_T.RN2903_MAC_TX_STATUS_TX_OK) + System.out.println("Transmit successful."); + else + { + // check to see if we got a downlink packet + if (trv == RN2903_MAC_TX_STATUS_T.RN2903_MAC_TX_STATUS_RX_RECEIVED) + { + System.out.println("Transmit successful, downlink packet received: " + + sensor.getResponse()); + } + else + { + System.out.println("Transmit failed with code " + trv); + } + } + } + else + { + System.out.println("Join failed with code " + rv); + } + + + System.out.println("Exiting..."); +// ! [Interesting] + } +} diff --git a/examples/java/RN2903_P2P_RX_Example.java b/examples/java/RN2903_P2P_RX_Example.java new file mode 100644 index 00000000..3c22f667 --- /dev/null +++ b/examples/java/RN2903_P2P_RX_Example.java @@ -0,0 +1,106 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 Intel Corporation. + * + * The MIT License + * + * 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. + */ + +import upm_rn2903.*; + +public class RN2903_P2P_RX_Example +{ + private static String defaultDev = "/dev/ttyUSB0"; + + public static void main(String[] args) throws InterruptedException + { +// ! [Interesting] + + if (args.length > 0) + defaultDev = args[0]; + + System.out.println("Using device " + defaultDev); + + // Instantiate a RN2903 sensor on defaultDev at 57600 baud. + RN2903 sensor = new RN2903(defaultDev, + javaupm_rn2903.RN2903_DEFAULT_BAUDRATE); + + // To use an internal UART understood by MRAA, use the following + // to inititialize rather than the above, which by default uses a + // tty path. + // + // RN2903 sensor = new RN2903(defaultDev, + // upm_rn2903.javaupm_rn2903.RN2903_DEFAULT_BAUDRATE); + + // enable debugging + // sensor.setDebug(true); + + // get version + if (sensor.command("sys get ver") + != RN2903_RESPONSE_T.RN2903_RESPONSE_OK) + { + System.out.println("Failed to retrieve device version string"); + System.exit(1); + } + System.out.println("Firmware version: " + sensor.getResponse()); + + System.out.println("Hardware EUI: " + sensor.getHardwareEUI()); + + // For this example, we will just try to receive a packet + // transmitted by the p2p-tx rn2903 example. We reset the + // device to defaults, and we do not make any adjustments to the + // radio configuration. You will probably want to do so for a + // real life application. + + // The first thing to do is to suspend the LoRaWAN stack on the device. + sensor.macPause(); + + // We will use continuous mode (window_size 0), though the default + // radio watch dog timer will expire every 15 seconds. We will + // just loop here. + + while (true) + { + System.out.println("Waiting for packet..."); + RN2903_RESPONSE_T rv = sensor.radioRx(0); + if (rv != RN2903_RESPONSE_T.RN2903_RESPONSE_OK) + { + System.out.println("radioRx() failed with code " + + rv.toString()); + } + else + { + String resp = sensor.getResponse(); + String payload = sensor.getRadioRxPayload(); + if (payload.length() == 0) + System.out.println("Got response: '" + resp + "'"); + else + System.out.println("Got payload: '" + + sensor.fromHex(payload) + + "'"); + } + + System.out.println(); + } + +// ! [Interesting] + } +} diff --git a/examples/java/RN2903_P2P_TX_Example.java b/examples/java/RN2903_P2P_TX_Example.java new file mode 100644 index 00000000..6edb6584 --- /dev/null +++ b/examples/java/RN2903_P2P_TX_Example.java @@ -0,0 +1,111 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 Intel Corporation. + * + * The MIT License + * + * 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. + */ + +import upm_rn2903.*; + +public class RN2903_P2P_TX_Example +{ + private static String defaultDev = "/dev/ttyUSB0"; + + public static void main(String[] args) throws InterruptedException + { +// ! [Interesting] + + if (args.length > 0) + defaultDev = args[0]; + + System.out.println("Using device " + defaultDev); + + // Instantiate a RN2903 sensor on defaultDev at 57600 baud. + RN2903 sensor = new RN2903(defaultDev, + javaupm_rn2903.RN2903_DEFAULT_BAUDRATE); + + // To use an internal UART understood by MRAA, use the following + // to inititialize rather than the above, which by default uses a + // tty path. + // + // RN2903 sensor = new RN2903(defaultDev, + // upm_rn2903.javaupm_rn2903.RN2903_DEFAULT_BAUDRATE); + + // enable debugging + // sensor.setDebug(true); + + // get version + if (sensor.command("sys get ver") + != RN2903_RESPONSE_T.RN2903_RESPONSE_OK) + { + System.out.println("Failed to retrieve device version string"); + System.exit(1); + } + System.out.println("Firmware version: " + sensor.getResponse()); + + System.out.println("Hardware EUI: " + sensor.getHardwareEUI()); + + // For this example, we will just try transmitting a packet over + // LoRa. We reset the device to defaults, and we do not make any + // adjustments to the radio configuration. You will probably want + // to do so for a real life application. + + // The first thing to do is to suspend the LoRaWAN stack on the device. + sensor.macPause(); + + // the default radio watchdog timer is set for 15 seconds, so we + // will send a packet every 10 seconds. In reality, local + // restrictions limit the amount of time on the air, so in a real + // implementation, you would not want to send packets that + // frequently. + + Integer count = 0; + while (true) + { + String output = "Ping " + count.toString(); + count++; + + // All payloads must be hex encoded + String payload = sensor.toHex(output); + + System.out.println("Transmitting a packet, data: '" + + output + + "' -> hex: '" + + payload + + "'"); + + RN2903_RESPONSE_T rv = sensor.radioTx(payload); + + if (rv == RN2903_RESPONSE_T.RN2903_RESPONSE_OK) + System.out.println("Transmit successful."); + else + System.out.println("Transmit failed with code " + + rv.toString()); + + System.out.println(); + + Thread.sleep(10000); + } + +// ! [Interesting] + } +} diff --git a/examples/javascript/rn2903-p2p-rx.js b/examples/javascript/rn2903-p2p-rx.js new file mode 100644 index 00000000..014ffd02 --- /dev/null +++ b/examples/javascript/rn2903-p2p-rx.js @@ -0,0 +1,103 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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. + */ + +var sensorObj = require('jsupm_rn2903'); + +var defaultDev = "/dev/ttyUSB0"; + +// if an argument was specified, use it as the device instead +if (process.argv.length > 2) +{ + defaultDev = process.argv[2]; +} + +console.log("Using device:", defaultDev); + +// Instantiate a RN2903 sensor on defaultDev at 57600 baud. +var sensor = new sensorObj.RN2903(defaultDev, + sensorObj.RN2903_DEFAULT_BAUDRATE); + +// To use an internal UART understood by MRAA, use the following +// to inititialize rather than the above, which by default uses a +// tty path. +// +// var sensor = new sensorObj.RN2903(0, +// sensorObj.RN2903_DEFAULT_BAUDRATE); + +// enable debugging +// sensor.setDebug(true); + +// get version +if (sensor.command("sys get ver")) +{ + console.log("Failed to retrieve device version string"); + process.exit(1); +} +console.log("Firmware version: " + sensor.getResponse()); + +console.log("Hardware EUI: " + sensor.getHardwareEUI()); + +// For this example, we will just try to receive a packet +// transmitted by the p2p-tx rn2903 example. We reset the +// device to defaults, and we do not make any adjustments to the +// radio configuration. You will probably want to do so for a +// real life application. + +// The first thing to do is to suspend the LoRaWAN stack on the device. +sensor.macPause(); + +// We will use continuous mode (window_size 0), though the default +// radio watch dog timer will expire every 15 seconds. We will +// just loop here. + +while (true) +{ + console.log("Waiting for packet..."); + var rv = sensor.radioRx(0); + if (rv) + { + console.log("radioRx() failed with code " + rv); + } + else + { + var resp = sensor.getResponse(); + var payload = sensor.getRadioRxPayload(); + if (!payload.length) + console.log("Got response: '" + resp + "'"); + else + console.log("Got payload: '" + + sensor.fromHex(payload) + + "'"); + } + + console.log(); +} + +process.on('SIGINT', function() { + sensor = null; + sensorObj.cleanUp(); + sensorObj = null; + console.log("Exiting..."); + process.exit(0); +}); diff --git a/examples/javascript/rn2903-p2p-tx.js b/examples/javascript/rn2903-p2p-tx.js new file mode 100644 index 00000000..45c15d7e --- /dev/null +++ b/examples/javascript/rn2903-p2p-tx.js @@ -0,0 +1,108 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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. + */ + +var sensorObj = require('jsupm_rn2903'); + +var defaultDev = "/dev/ttyUSB0"; + +// if an argument was specified, use it as the device instead +if (process.argv.length > 2) +{ + defaultDev = process.argv[2]; +} + +console.log("Using device:", defaultDev); + +// Instantiate a RN2903 sensor on defaultDev at 57600 baud. +var sensor = new sensorObj.RN2903(defaultDev, + sensorObj.RN2903_DEFAULT_BAUDRATE); + +// To use an internal UART understood by MRAA, use the following +// to inititialize rather than the above, which by default uses a +// tty path. +// +// var sensor = new sensorObj.RN2903(0, +// sensorObj.RN2903_DEFAULT_BAUDRATE); + +// enable debugging +// sensor.setDebug(true); + +// get version +if (sensor.command("sys get ver")) +{ + console.log("Failed to retrieve device version string"); + process.exit(1); +} +console.log("Firmware version: " + sensor.getResponse()); + +console.log("Hardware EUI: " + sensor.getHardwareEUI()); + +// For this example, we will just try transmitting a packet over +// LoRa. We reset the device to defaults, and we do not make any +// adjustments to the radio configuration. You will probably want +// to do so for a real life application. + +// The first thing to do is to suspend the LoRaWAN stack on the device. +sensor.macPause(); + +// the default radio watchdog timer is set for 15 seconds, so we +// will send a packet every 10 seconds. In reality, local +// restrictions limit the amount of time on the air, so in a real +// implementation, you would not want to send packets that +// frequently. + +var count = 0; +function transmit() +{ + var output = "Ping " + count; + count++; + + // All payloads must be hex encoded + var payload = sensor.toHex(output); + + console.log("Transmitting a packet, data: '" + + output + + "' -> hex: '" + + payload + + "'"); + + var rv = sensor.radioTx(payload); + + if (rv == sensorObj.RN2903_RESPONSE_OK) + console.log("Transmit successful."); + else + console.log("Transmit failed with code " + rv); + + console.log(); +} + +setInterval(transmit, 10000); + +process.on('SIGINT', function() { + sensor = null; + sensorObj.cleanUp(); + sensorObj = null; + console.log("Exiting..."); + process.exit(0); +}); diff --git a/examples/javascript/rn2903.js b/examples/javascript/rn2903.js new file mode 100644 index 00000000..99042993 --- /dev/null +++ b/examples/javascript/rn2903.js @@ -0,0 +1,131 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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. + */ + +var sensorObj = require('jsupm_rn2903'); + +var defaultDev = "/dev/ttyUSB0"; + +// if an argument was specified, use it as the device instead +if (process.argv.length > 2) +{ + defaultDev = process.argv[2]; +} + +console.log("Using device:", defaultDev); + +// Instantiate a RN2903 sensor on defaultDev at 57600 baud. +var sensor = new sensorObj.RN2903(defaultDev, + sensorObj.RN2903_DEFAULT_BAUDRATE); + +// To use an internal UART understood by MRAA, use the following +// to inititialize rather than the above, which by default uses a +// tty path. +// +// var sensor = new sensorObj.RN2903(0, +// sensorObj.RN2903_DEFAULT_BAUDRATE); + +// enable debugging +// sensor.setDebug(true); + +// get version +if (sensor.command("sys get ver")) +{ + console.log("Failed to retrieve device version string"); + process.exit(1); +} +console.log("Firmware version: " + sensor.getResponse()); + +console.log("Hardware EUI: " + sensor.getHardwareEUI()); + +// we can support two types of join, OTAA and ABP. Each requires +// that certain parameters be set first. We will only attempt ABP +// joining with this example since it's the only one that can +// succeed without actual configuration. In both cases, if you +// are actually attempting to join a real LoRaWAN network, you +// must change the parameters below to match the network you are +// attempting to join. + +// For OTAA, you need to supply valid Device EUI, Application EUI, +// and Application key: +// +// sensor.setDeviceEUI("0011223344556677"); +// sensor.setApplicationEUI("0011223344556677"); +// sensor.setApplicationKey("01234567012345670123456701234567"); +// +// RN2903_JOIN_STATUS_T rv = sensor.join(RN2903_JOIN_TYPE_OTAA); +// A successful join will return RN2903_JOIN_STATUS_ACCEPTED (0). +// cout << "JOIN: got rv " << int(rv) << endl; + +// Try an ABP join. Note, these parameters are made up. For a +// real network, you will want to use the correct values +// obviously. For an ABP join, you need to supply the Device +// Address, Network Session Key, and the Application Session Key. + +sensor.setDeviceAddr("00112233"); +sensor.setNetworkSessionKey("00112233001122330011223300112233"); +sensor.setApplicationSessionKey("00112233001122330011223300112233"); + +var rv = sensor.join(sensorObj.RN2903_JOIN_TYPE_ABP); +if (rv == sensorObj.RN2903_JOIN_STATUS_ACCEPTED) +{ + console.log("Join successful."); + + // All transmit payloads must be hex encoded strings, so + // pretend we have a temperature sensor that gave us a value + // of 25.6 C, and we want to transmit it. + var faketemp = "25.6"; + console.log("Transmitting a packet..."); + + var trv = sensor.macTx(sensorObj.RN2903_MAC_MSG_TYPE_UNCONFIRMED, + 1, // port number + sensor.toHex(faketemp)); + + if (trv == sensorObj.RN2903_MAC_TX_STATUS_TX_OK) + console.log("Transmit successful."); + else + { + // check to see if we got a downlink packet + if (trv == sensorObj.RN2903_MAC_TX_STATUS_RX_RECEIVED) + { + console.log("Transmit successful, downlink packet received: " + + sensor.getResponse()); + } + else + { + console.log("Transmit failed with code " + trv); + } + } +} +else +{ + console.log("Join failed with code " + rv); +} + + +console.log("Exiting"); + +sensor = null; +sensorObj.cleanUp(); +sensorObj = null; +process.exit(0); diff --git a/examples/python/rn2903-p2p-rx.py b/examples/python/rn2903-p2p-rx.py new file mode 100755 index 00000000..108728ac --- /dev/null +++ b/examples/python/rn2903-p2p-rx.py @@ -0,0 +1,108 @@ +#!/usr/bin/python +# Author: Jon Trulson +# Copyright (c) 2017 Intel Corporation. +# +# The MIT License +# +# 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. + +from __future__ import print_function +import time, sys, signal, atexit +from upm import pyupm_rn2903 as sensorObj + +def main(): + ## Exit handlers ## + # This function stops python from printing a stacktrace when you + # hit control-C + def SIGINTHandler(signum, frame): + raise SystemExit + + # This function lets you run code on exit + def exitHandler(): + print("Exiting") + sys.exit(0) + + # Register exit handlers + atexit.register(exitHandler) + signal.signal(signal.SIGINT, SIGINTHandler) + + defaultDev = "/dev/ttyUSB0" + if (len(sys.argv) > 1): + defaultDev = sys.argv[1] + print("Using device", defaultDev) + + # Instantiate a RN2903 sensor on defaultDev at 57600 baud. + sensor = sensorObj.RN2903(defaultDev, + sensorObj.RN2903_DEFAULT_BAUDRATE) + + # To use an internal UART understood by MRAA, use the following + # to inititialize rather than the above, which by default uses a + # tty path. + # + # sensor = sensorObj.RN2903(0, + # sensorObj.RN2903_DEFAULT_BAUDRATE) + + # enable debugging + # sensor.setDebug(True) + + # get version + if (sensor.command("sys get ver")): + print("Failed to retrieve device version string") + sys.exit(1) + + print("Firmware version: " + sensor.getResponse()) + + print("Hardware EUI: " + sensor.getHardwareEUI()) + + # For this example, we will just try to receive a packet + # transmitted by the p2p-tx rn2903 example. We reset the + # device to defaults, and we do not make any adjustments to the + # radio configuration. You will probably want to do so for a + # real life application. + + # The first thing to do is to suspend the LoRaWAN stack on the device. + sensor.macPause(); + + # We will use continuous mode (window_size 0), though the default + # radio watch dog timer will expire every 15 seconds. We will + # just loop here. + + while (True): + print("Waiting for packet...") + rv = sensor.radioRx(0); + if (rv): + print("radioRx() failed with code " + str(rv)) + else: + resp = sensor.getResponse(); + payload = sensor.getRadioRxPayload(); + if (not len(payload)): + print("Got response: '", end='') + print(resp, end="") + print("'") + else: + print("Got payload: '", end='') + print(sensor.fromHex(payload), end='') + print("'") + + print() + + +if __name__ == '__main__': + main() diff --git a/examples/python/rn2903-p2p-tx.py b/examples/python/rn2903-p2p-tx.py new file mode 100755 index 00000000..a552ff34 --- /dev/null +++ b/examples/python/rn2903-p2p-tx.py @@ -0,0 +1,114 @@ +#!/usr/bin/python +# Author: Jon Trulson +# Copyright (c) 2017 Intel Corporation. +# +# The MIT License +# +# 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. + +from __future__ import print_function +import time, sys, signal, atexit +from upm import pyupm_rn2903 as sensorObj + +def main(): + ## Exit handlers ## + # This function stops python from printing a stacktrace when you + # hit control-C + def SIGINTHandler(signum, frame): + raise SystemExit + + # This function lets you run code on exit + def exitHandler(): + print("Exiting") + sys.exit(0) + + # Register exit handlers + atexit.register(exitHandler) + signal.signal(signal.SIGINT, SIGINTHandler) + + defaultDev = "/dev/ttyUSB0" + if (len(sys.argv) > 1): + defaultDev = sys.argv[1] + print("Using device", defaultDev) + + # Instantiate a RN2903 sensor on defaultDev at 57600 baud. + sensor = sensorObj.RN2903(defaultDev, + sensorObj.RN2903_DEFAULT_BAUDRATE) + + # To use an internal UART understood by MRAA, use the following + # to inititialize rather than the above, which by default uses a + # tty path. + # + # sensor = sensorObj.RN2903(0, + # sensorObj.RN2903_DEFAULT_BAUDRATE) + + # enable debugging + # sensor.setDebug(True) + + # get version + if (sensor.command("sys get ver")): + print("Failed to retrieve device version string") + sys.exit(1) + + print("Firmware version: " + sensor.getResponse()) + + print("Hardware EUI: " + sensor.getHardwareEUI()) + + # For this example, we will just try transmitting a packet over + # LoRa. We reset the device to defaults, and we do not make any + # adjustments to the radio configuration. You will probably want + # to do so for a real life application. + + # The first thing to do is to suspend the LoRaWAN stack on the device. + sensor.macPause(); + + # the default radio watchdog timer is set for 15 seconds, so we + # will send a packet every 10 seconds. In reality, local + # restrictions limit the amount of time on the air, so in a real + # implementation, you would not want to send packets that + # frequently. + + count = 0; + + while (True): + output = "Ping " + str(count) + count += 1 + # All payloads must be hex encoded + payload = sensor.toHex(output); + + print("Transmitting a packet, data: '", end='') + print(output, end='') + print("' -> hex: '", end='') + print(payload, end='') + print("'") + + rv = sensor.radioTx(payload); + + if (rv == sensorObj.RN2903_RESPONSE_OK): + print("Transmit successful.") + else: + print("Transmit failed with code " + str(rv)) + + print() + time.sleep(10) + + +if __name__ == '__main__': + main() diff --git a/examples/python/rn2903.py b/examples/python/rn2903.py new file mode 100755 index 00000000..cd5731bb --- /dev/null +++ b/examples/python/rn2903.py @@ -0,0 +1,129 @@ +#!/usr/bin/python +# Author: Jon Trulson +# Copyright (c) 2017 Intel Corporation. +# +# The MIT License +# +# 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. + +from __future__ import print_function +import time, sys, signal, atexit +from upm import pyupm_rn2903 as sensorObj + +def main(): + ## Exit handlers ## + # This function stops python from printing a stacktrace when you + # hit control-C + def SIGINTHandler(signum, frame): + raise SystemExit + + # This function lets you run code on exit + def exitHandler(): + print("Exiting") + sys.exit(0) + + # Register exit handlers + atexit.register(exitHandler) + signal.signal(signal.SIGINT, SIGINTHandler) + + defaultDev = "/dev/ttyUSB0" + if (len(sys.argv) > 1): + defaultDev = sys.argv[1] + print("Using device", defaultDev) + + # Instantiate a RN2903 sensor on defaultDev at 57600 baud. + sensor = sensorObj.RN2903(defaultDev, + sensorObj.RN2903_DEFAULT_BAUDRATE) + + # To use an internal UART understood by MRAA, use the following + # to inititialize rather than the above, which by default uses a + # tty path. + # + # sensor = sensorObj.RN2903(0, + # sensorObj.RN2903_DEFAULT_BAUDRATE) + + # enable debugging + # sensor.setDebug(True) + + # get version + if (sensor.command("sys get ver")): + print("Failed to retrieve device version string") + sys.exit(1) + + print("Firmware version: " + sensor.getResponse()) + + print("Hardware EUI: " + sensor.getHardwareEUI()) + + # we can support two types of join, OTAA and ABP. Each requires + # that certain parameters be set first. We will only attempt ABP + # joining with this example since it's the only one that can + # succeed without actual configuration. In both cases, if you + # are actually attempting to join a real LoRaWAN network, you + # must change the parameters below to match the network you are + # attempting to join. + + # For OTAA, you need to supply valid Device EUI, Application EUI, + # and Application key: + # + # sensor.setDeviceEUI("0011223344556677") + # sensor.setApplicationEUI("0011223344556677") + # sensor.setApplicationKey("01234567012345670123456701234567") + # + # rv = sensor.join(RN2903_JOIN_TYPE_OTAA) + # A successful join will return RN2903_JOIN_STATUS_ACCEPTED (0). + + # Try an ABP join. Note, these parameters are made up. For a + # real network, you will want to use the correct values + # obviously. For an ABP join, you need to supply the Device + # Address, Network Session Key, and the Application Session Key. + + sensor.setDeviceAddr("00112233") + sensor.setNetworkSessionKey("00112233001122330011223300112233") + sensor.setApplicationSessionKey("00112233001122330011223300112233") + + rv = sensor.join(sensorObj.RN2903_JOIN_TYPE_ABP) + + if (rv == sensorObj.RN2903_JOIN_STATUS_ACCEPTED): + print("Join successful.") + + # All transmit payloads must be hex encoded strings, so + # pretend we have a temperature sensor that gave us a value + # of 25.6 C, and we want to transmit it. + faketemp = "25.6" + print("Transmitting a packet...") + + trv = sensor.macTx(sensorObj.RN2903_MAC_MSG_TYPE_UNCONFIRMED, + 1, # port number + sensor.toHex(faketemp)) + + if (trv == sensorObj.RN2903_MAC_TX_STATUS_TX_OK): + print("Transmit successful.") + else: + # check to see if we got a downlink packet + if (trv == sensor.Obj.RN2903_MAC_TX_STATUS_RX_RECEIVED): + print("Transmit successful, downlink packet received:", end=' ') + print(sensor.getResponse()) + else: + print("Transmit failed with code " + str(trv)) + else: + print("Join failed with code " + str(rv)) + +if __name__ == '__main__': + main() diff --git a/src/rn2903/CMakeLists.txt b/src/rn2903/CMakeLists.txt new file mode 100644 index 00000000..f5d84c63 --- /dev/null +++ b/src/rn2903/CMakeLists.txt @@ -0,0 +1,8 @@ +upm_mixed_module_init (NAME rn2903 + DESCRIPTION "LoRaWAN transceiver" + C_HDR rn2903.h rn2903_defs.h + C_SRC rn2903.c + CPP_HDR rn2903.hpp + CPP_SRC rn2903.cxx + CPP_WRAPS_C + REQUIRES mraa utilities-c) diff --git a/src/rn2903/javaupm_rn2903.i b/src/rn2903/javaupm_rn2903.i new file mode 100644 index 00000000..104fe35a --- /dev/null +++ b/src/rn2903/javaupm_rn2903.i @@ -0,0 +1,22 @@ +%module javaupm_rn2903 +%include "../upm.i" +%include "std_string.i" +%include "stdint.i" +%include "typemaps.i" + +%include "rn2903_defs.h" +%include "rn2903.hpp" +%{ + #include "rn2903.hpp" +%} + +%pragma(java) jniclasscode=%{ + static { + try { + System.loadLibrary("javaupm_rn2903"); + } catch (UnsatisfiedLinkError e) { + System.err.println("Native code library failed to load. \n" + e); + System.exit(1); + } + } +%} diff --git a/src/rn2903/jsupm_rn2903.i b/src/rn2903/jsupm_rn2903.i new file mode 100644 index 00000000..498bc4b3 --- /dev/null +++ b/src/rn2903/jsupm_rn2903.i @@ -0,0 +1,9 @@ +%module jsupm_rn2903 +%include "../upm.i" +%include "std_string.i" + +%include "rn2903_defs.h" +%include "rn2903.hpp" +%{ + #include "rn2903.hpp" +%} diff --git a/src/rn2903/pyupm_rn2903.i b/src/rn2903/pyupm_rn2903.i new file mode 100644 index 00000000..5834df06 --- /dev/null +++ b/src/rn2903/pyupm_rn2903.i @@ -0,0 +1,13 @@ +// Include doxygen-generated documentation +%include "pyupm_doxy2swig.i" +%module pyupm_rn2903 +%include "../upm.i" +%include "std_string.i" + +%feature("autodoc", "3"); + +%include "rn2903_defs.h" +%include "rn2903.hpp" +%{ + #include "rn2903.hpp" +%} diff --git a/src/rn2903/rn2903.c b/src/rn2903/rn2903.c new file mode 100644 index 00000000..de93fb9a --- /dev/null +++ b/src/rn2903/rn2903.c @@ -0,0 +1,1163 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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 +#include +#include + +#include "rn2903.h" + +#include "upm_utilities.h" +#include "upm_platform.h" + +// we use small buffers of this size to build certain compound +// commands +#define RN2903_CMD_BUFFER_32B (32) // 32 bytes + +// maximum number of autobaud retries +#define RN2903_AUTOBAUD_RETRIES (10) + +// some useful macros to save on typing and text wrapping +#undef _SHIFT +#define _SHIFT(x) (_RN2903_##x##_SHIFT) + +#undef _MASK +#define _MASK(x) (_RN2903_##x##_MASK) + +#undef _SHIFTMASK +#define _SHIFTMASK(x) (_MASK(x) << _SHIFT(x)) + +// disable printf to stdout if on Zephyr, and stdout isn't available. +#if defined(UPM_PLATFORM_ZEPHYR) && !defined(CONFIG_STDOUT_CONSOLE) +# define printf printk +#endif + + +static bool validate_hex_str(const char *hex) +{ + assert(hex != NULL); + + int len = strlen(hex); + if ((len % 2) != 0) + { + printf("%s: strlen(hex) must be a multiple of 2\n", + __FUNCTION__); + return false; + } + + for (int i=0; i= '0' && hex[i] <= '9') || + (tolower(hex[i]) >= 'a' && tolower(hex[i]) <= 'f')) ) + { + printf("%s: invalid hex character at position %d\n", + __FUNCTION__, i); + return false; + } + } + + return true; +} + +static rn2903_context _rn2903_preinit() +{ + // 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; + } + + rn2903_context dev = + (rn2903_context)malloc(sizeof(struct _rn2903_context)); + + if (!dev) + return NULL; + + // zero out context + memset((void *)dev, 0, sizeof(struct _rn2903_context)); + + // first response wait time + dev->cmd_resp_wait_ms = RN2903_DEFAULT_RESP_DELAY; + // optional second response wait time + dev->cmd_resp2_wait_ms = RN2903_DEFAULT_RESP2_DELAY; + + // init stored baudrate to RN2903_DEFAULT_BAUDRATE + dev->baudrate = RN2903_DEFAULT_BAUDRATE; + + // uncomment for "early" debugging + // dev->debug = true; + + return dev; +} + +static rn2903_context _rn2903_postinit(rn2903_context dev, + unsigned int baudrate) +{ + assert(dev != NULL); + + if (rn2903_set_baudrate(dev, baudrate)) + { + printf("%s: rn2903_set_baudrate() failed.\n", __FUNCTION__); + rn2903_close(dev); + return NULL; + } + + if (rn2903_set_flow_control(dev, RN2903_FLOW_CONTROL_NONE)) + { + printf("%s: rn2903_set_flow_control() failed.\n", __FUNCTION__); + rn2903_close(dev); + return NULL; + } + + // turn off debugging + rn2903_set_debug(dev, false); + + // reset the device + if (rn2903_reset(dev)) + { + printf("%s: rn2903_reset() failed.\n", __FUNCTION__); + rn2903_close(dev); + return NULL; + } + + // now get and store our hardware EUI + if (rn2903_command(dev, "sys get hweui")) + { + printf("%s: rn2903_command(sys get hweui) failed.\n", __FUNCTION__); + rn2903_close(dev); + return NULL; + } + strncpy(dev->hardware_eui, dev->resp_data, RN2903_MAX_HEX_EUI64); + + return dev; +} + +// uart init +rn2903_context rn2903_init(unsigned int uart, unsigned int baudrate) +{ + rn2903_context dev; + + if (!(dev = _rn2903_preinit())) + return NULL; + + // initialize the MRAA context + + // uart, default should be 8N1 + if (!(dev->uart = mraa_uart_init(uart))) + { + printf("%s: mraa_uart_init() failed.\n", __FUNCTION__); + rn2903_close(dev); + return NULL; + } + + return _rn2903_postinit(dev, baudrate); +} + +// uart tty init +rn2903_context rn2903_init_tty(const char *uart_tty, unsigned int baudrate) +{ + rn2903_context dev; + + if (!(dev = _rn2903_preinit())) + return NULL; + + // initialize the MRAA context + + // uart, default should be 8N1 + if (!(dev->uart = mraa_uart_init_raw(uart_tty))) + { + printf("%s: mraa_uart_init_raw() failed.\n", __FUNCTION__); + rn2903_close(dev); + return NULL; + } + + return _rn2903_postinit(dev, baudrate); +} + +void rn2903_close(rn2903_context dev) +{ + assert(dev != NULL); + + if (dev->to_hex_buf) + free(dev->to_hex_buf); + if (dev->from_hex_buf) + free(dev->from_hex_buf); + + if (dev->uart) + mraa_uart_stop(dev->uart); + + free(dev); +} + +int rn2903_read(const rn2903_context dev, char *buffer, size_t len) +{ + assert(dev != NULL); + + // uart + return mraa_uart_read(dev->uart, buffer, len); +} + +int rn2903_write(const rn2903_context dev, const char *buffer, size_t len) +{ + assert(dev != NULL); + + int rv = mraa_uart_write(dev->uart, buffer, len); + mraa_uart_flush(dev->uart); + + return rv; +} + +bool rn2903_data_available(const rn2903_context dev, unsigned int millis) +{ + assert(dev != NULL); + + if (mraa_uart_data_available(dev->uart, millis)) + return true; + else + return false; +} + +upm_result_t rn2903_set_baudrate(const rn2903_context dev, + unsigned int baudrate) +{ + assert(dev != NULL); + + if (dev->debug) + printf("%s: Setting baudrate to %d\n", __FUNCTION__, + baudrate); + + if (mraa_uart_set_baudrate(dev->uart, baudrate)) + { + printf("%s: mraa_uart_set_baudrate() failed.\n", __FUNCTION__); + return UPM_ERROR_OPERATION_FAILED; + } + + dev->baudrate = baudrate; + + if (!rn2903_autobaud(dev, RN2903_AUTOBAUD_RETRIES)) + { + printf("%s: rn2903_autobaud detection failed.\n", __FUNCTION__); + return UPM_ERROR_OPERATION_FAILED; + } + + return UPM_SUCCESS; +} + +void rn2903_set_debug(const rn2903_context dev, bool enable) +{ + assert(dev != NULL); + + dev->debug = enable; +} + +void rn2903_set_response_wait_time(const rn2903_context dev, + unsigned int wait_time) +{ + assert(dev != NULL); + + dev->cmd_resp_wait_ms = wait_time; +} + +void rn2903_set_response2_wait_time(const rn2903_context dev, + unsigned int wait_time) +{ + assert(dev != NULL); + + dev->cmd_resp2_wait_ms = wait_time; +} + +void rn2903_drain(const rn2903_context dev) +{ + assert(dev != NULL); + + char resp[RN2903_MAX_BUFFER]; + int rv; + while (rn2903_data_available(dev, 0)) + { + rv = rn2903_read(dev, resp, RN2903_MAX_BUFFER); + if (rv < 0) + { + printf("%s: read failed\n", __FUNCTION__); + return; + } + // printf("%s: Tossed %d bytes\n", __FUNCTION__, rv); + } + + return; +} + +RN2903_RESPONSE_T rn2903_waitfor_response(const rn2903_context dev, + int wait_ms) +{ + assert(dev != NULL); + + memset(dev->resp_data, 0, RN2903_MAX_BUFFER); + dev->resp_len = 0; + + upm_clock_t clock; + upm_clock_init(&clock); + uint32_t elapsed = 0; + + do + { + if (rn2903_data_available(dev, 1)) + { + int rv = rn2903_read(dev, &(dev->resp_data[dev->resp_len]), 1); + + if (rv < 0) + return RN2903_RESPONSE_UPM_ERROR; + + // discard CR's + if (dev->resp_data[dev->resp_len] == '\r') + continue; + + // got a LF, we are done - discard and finish + if (dev->resp_data[dev->resp_len] == '\n') + { + dev->resp_data[dev->resp_len] = 0; + break; + } + + // too much data? + if (dev->resp_len >= RN2903_MAX_BUFFER - 1) + break; + + dev->resp_len++; + } + } while ( (elapsed = upm_elapsed_ms(&clock)) < wait_ms); + + if (dev->debug) + printf("\tRESP (%d): '%s'\n", (int)dev->resp_len, + (dev->resp_len) ? dev->resp_data : ""); + + // check for and return obvious errors + if (elapsed >= wait_ms) + return RN2903_RESPONSE_TIMEOUT; + else if (rn2903_find(dev, RN2903_PHRASE_INV_PARAM)) + return RN2903_RESPONSE_INVALID_PARAM; + else + return RN2903_RESPONSE_OK; // either data or "ok" +} + +RN2903_RESPONSE_T rn2903_command(const rn2903_context dev, const char *cmd) +{ + assert(dev != NULL); + assert(cmd != NULL); + + rn2903_drain(dev); + + if (dev->debug) + printf("CMD: '%s'\n", cmd); + + if (rn2903_write(dev, cmd, strlen(cmd)) < 0) + { + printf("%s: rn2903_write(cmd) failed\n", __FUNCTION__); + return RN2903_RESPONSE_UPM_ERROR; + } + + // now write the termination string (CR/LF) + if (rn2903_write(dev, RN2903_PHRASE_TERM, RN2903_PHRASE_TERM_LEN) < 0) + { + printf("%s: rn2903_write(TERM) failed\n", __FUNCTION__); + return RN2903_RESPONSE_UPM_ERROR; + } + + return rn2903_waitfor_response(dev, dev->cmd_resp_wait_ms); +} + +RN2903_RESPONSE_T rn2903_command_with_arg(const rn2903_context dev, + const char *cmd, const char *arg) +{ + assert(dev != NULL); + assert(cmd != NULL); + assert(arg != NULL); + + // cmdarg<0-terminator> + int buflen = strlen(cmd) + 1 + strlen(arg) + 1; + char buf[buflen]; + memset(buf, 0, buflen); + + snprintf(buf, buflen, "%s %s", cmd, arg); + + return rn2903_command(dev, buf); +} + +const char *rn2903_get_response(const rn2903_context dev) +{ + assert(dev != NULL); + + return dev->resp_data; +} + +size_t rn2903_get_response_len(const rn2903_context dev) +{ + assert(dev != NULL); + + return dev->resp_len; +} + +const char *rn2903_to_hex(const rn2903_context dev, const char *src, int len) +{ + assert(dev != NULL); + assert(src != NULL); + + static const char hdigits[16] = "0123456789ABCDEF"; + + // first free previous destination hex buffer if allocated + if (dev->to_hex_buf) + { + free(dev->to_hex_buf); + dev->to_hex_buf = NULL; + } + + if (len == 0) + return NULL; + + int dlen = (len * 2) + 1; + + if (!(dev->to_hex_buf = malloc(dlen))) + { + printf("%s: malloc(%d) failed\n", __FUNCTION__, dlen); + return NULL; + } + memset(dev->to_hex_buf, 0, dlen); + + char *dptr = dev->to_hex_buf; + char *sptr = (char *)src; + for (int i=0; i> 4) & 0x0f]; + *dptr++ = hdigits[(sptr[i] & 0x0f)]; + } + + // the memset() will have ensured the last byte is 0 + return dev->to_hex_buf; +} + +const char *rn2903_from_hex(const rn2903_context dev, + const char *src) +{ + assert(dev != NULL); + assert(src != NULL); + + // first free previous destination hex buffer if allocated + if (dev->from_hex_buf) + { + free(dev->from_hex_buf); + dev->from_hex_buf = NULL; + } + + int len = strlen(src); + if (len == 0) + return NULL; + + if (!validate_hex_str(src)) + return NULL; + + // add a byte for 0 termination, just in case we're decoding a + // string + int dlen = (len / 2) + 1; + + if (!(dev->from_hex_buf = malloc(dlen))) + { + printf("%s: malloc(%d) failed\n", __FUNCTION__, dlen); + return NULL; + } + memset(dev->from_hex_buf, 0, dlen); + + char *dptr = dev->from_hex_buf; + for (int i=0; i<(dlen - 1); i++) + { + char tbuf[3] = { src[i*2], src[(i*2)+1], 0 }; + *dptr++ = (char)strtol(tbuf, NULL, 16); + } + + return dev->from_hex_buf; +} + +const char *rn2903_get_hardware_eui(const rn2903_context dev) +{ + assert(dev != NULL); + + return dev->hardware_eui; +} + +upm_result_t rn2903_update_mac_status(const rn2903_context dev) +{ + assert(dev != NULL); + + if (rn2903_command(dev, "mac get status")) + { + printf("%s: rn2903_command(mac get status) failed.\n", __FUNCTION__); + return UPM_ERROR_OPERATION_FAILED; + } + + // make sure we actually got a hex value of 4 bytes + if (!validate_hex_str(dev->resp_data) || dev->resp_len != 4) + { + printf("%s: invalid mac status.\n", __FUNCTION__); + return UPM_ERROR_OPERATION_FAILED; + } + + // convert it + const char *statPtr = rn2903_from_hex(dev, dev->resp_data); + if (!statPtr) + { + printf("%s: from_hex conversion failed.\n", __FUNCTION__); + return UPM_ERROR_OPERATION_FAILED; + } + + // now play pointer games. We should have 2 bytes which we'll + // turn into a uint16_t (LE), which we will then stuff into our + // mac_status_word field in the context. + uint16_t status16 = (statPtr[0] << 8) | statPtr[1]; + + // store the mac_status_word, then decode the actual mac_status + // (state) enumeration + + dev->mac_status_word = status16; + dev->mac_mac_status = + (RN2903_MAC_STATUS_T)((status16 & _SHIFTMASK(MAC_STATUS_MAC_STATUS)) + >> _SHIFT(MAC_STATUS_MAC_STATUS)); + + return UPM_SUCCESS; +} + +uint16_t rn2903_get_mac_status_word(const rn2903_context dev) +{ + assert(dev != NULL); + + return dev->mac_status_word; +} + +RN2903_MAC_STATUS_T rn2903_get_mac_status(const rn2903_context dev) +{ + assert(dev != NULL); + + return dev->mac_mac_status; +} + +upm_result_t rn2903_reset(const rn2903_context dev) +{ + assert(dev != NULL); + + rn2903_autobaud(dev, RN2903_AUTOBAUD_RETRIES); + + if (rn2903_command(dev, "sys reset")) + { + // this command will reset the baudrate back to the default if + // we changed it previously. We do not report an error here, + // if we are not using the default baudrate, since we've now + // switched to a different baudrate than we had, and cannot + // read the response anyway. + if (dev->baudrate == RN2903_DEFAULT_BAUDRATE) + return UPM_ERROR_OPERATION_FAILED; + } + + // to be safe, always set the baudrate after a reset + if (rn2903_set_baudrate(dev, dev->baudrate)) + return UPM_ERROR_OPERATION_FAILED; + + upm_delay_ms(100); + + return UPM_SUCCESS; +} + +RN2903_JOIN_STATUS_T rn2903_join(const rn2903_context dev, + RN2903_JOIN_TYPE_T type) +{ + assert(dev != NULL); + + // first, do a couple of initial checks... + + // get the mac status and ensure that 1) we are not already + // joined, 2) the mac status is idle, 3) we have not been + // silenced, and 4) MAC has not been paused. + + if (rn2903_update_mac_status(dev)) + { + printf("%s: rn2903_update_mac_status() failed\n", __FUNCTION__); + return RN2903_JOIN_STATUS_UPM_ERROR; + } + + // if the radio is not idle, we aren't going anywhere + RN2903_MAC_STATUS_T mac_status = rn2903_get_mac_status(dev); + if (mac_status != RN2903_MAC_STAT_IDLE) + return RN2903_JOIN_STATUS_BUSY; + + // now check the rest of the status bits... + uint16_t status = rn2903_get_mac_status_word(dev); + if (status & RN2903_MAC_STATUS_JOINED) + return RN2903_JOIN_STATUS_ALREADY_JOINED; + else if (status & RN2903_MAC_STATUS_SILENT) + return RN2903_JOIN_STATUS_SILENT; + else if (status & RN2903_MAC_STATUS_PAUSED) + return RN2903_JOIN_STATUS_MAC_PAUSED; + + // so far, so good... now build the command + + char cmd[RN2903_CMD_BUFFER_32B] = {}; + snprintf(cmd, RN2903_CMD_BUFFER_32B, "mac join %s", + (type == RN2903_JOIN_TYPE_OTAA) ? "otaa" : "abp"); + + // run the command. We will get two responses back - one + // immediately if there is an error or if the join operation was + // successfully submitted to the radio for transmission, and + // another indicating whether the join was granted, or failed. + // ABP joins will always succeed immediately. + + RN2903_RESPONSE_T rv; + if ((rv = rn2903_command(dev, cmd))) + { + // a failure of some sort. We've already screened for most of + // them, but there are a couple that we can't detect until we + // try (UPM). + printf("%s: join command failed (%d).\n", __FUNCTION__, rv); + return RN2903_JOIN_STATUS_UPM_ERROR; + } + + // if we are here, then we either got an "ok" or another error we + // couldn't screen for. Check for them. + + if (rn2903_find(dev, "no_free_ch")) + return RN2903_JOIN_STATUS_NO_CHAN; + else if (rn2903_find(dev, "keys_not_init")) + return RN2903_JOIN_STATUS_BAD_KEYS; + + // now we wait awhile for another response indicating whether the + // join request was accepted or not + + if ((rv = rn2903_waitfor_response(dev, dev->cmd_resp2_wait_ms))) + { + printf("%s: join second response failed (%d).\n", __FUNCTION__, rv); + return RN2903_JOIN_STATUS_UPM_ERROR; + } + + if (rn2903_find(dev, "denied")) + return RN2903_JOIN_STATUS_DENIED; + else if (rn2903_find(dev, "accepted")) + return RN2903_JOIN_STATUS_ACCEPTED; + + // if it's anything else, we failed :( + printf("%s: unexpected response to join request.\n", __FUNCTION__); + + return RN2903_JOIN_STATUS_UPM_ERROR; +} + +upm_result_t rn2903_set_flow_control(const rn2903_context dev, + RN2903_FLOW_CONTROL_T fc) +{ + assert(dev != NULL); + + mraa_result_t rv = MRAA_SUCCESS; + + switch(fc) + { + case RN2903_FLOW_CONTROL_NONE: + rv = mraa_uart_set_flowcontrol(dev->uart, false, false); + break; + + case RN2903_FLOW_CONTROL_HARD: + rv = mraa_uart_set_flowcontrol(dev->uart, false, true); + break; + + default: + return UPM_ERROR_INVALID_PARAMETER; + } + + if (rv == MRAA_SUCCESS) + return UPM_SUCCESS; + else + return UPM_ERROR_OPERATION_FAILED; +} + +bool rn2903_find(const rn2903_context dev, const char *str) +{ + assert(dev != NULL); + assert(str != NULL); + + return ((strstr(dev->resp_data, str) == dev->resp_data) ? true : false); +} + +upm_result_t rn2903_set_device_eui(const rn2903_context dev, + const char *str) +{ + assert(dev != NULL); + assert(str != NULL); + + // first verify that the string is a valid hex string of the right + // size (16 bytes for the 8 byte EUI) + + if (!validate_hex_str(str) || strlen(str) != 16) + { + printf("%s: invalid 16 byte device EUI hex string.\n", __FUNCTION__); + return UPM_ERROR_OPERATION_FAILED; + } + + if (rn2903_command_with_arg(dev, "mac set deveui", str)) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t rn2903_get_device_eui(const rn2903_context dev) +{ + assert(dev != NULL); + + if (rn2903_command(dev, "mac get deveui")) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t rn2903_set_application_eui(const rn2903_context dev, + const char *str) +{ + assert(dev != NULL); + assert(str != NULL); + + // first verify that the string is a valid hex string of the right + // size (16 bytes for the 8 byte EUI) + + if (!validate_hex_str(str) || strlen(str) != 16) + { + printf("%s: invalid 16 byte application EUI hex string.\n", + __FUNCTION__); + return UPM_ERROR_OPERATION_FAILED; + } + + if (rn2903_command_with_arg(dev, "mac set appeui", str)) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t rn2903_get_application_eui(const rn2903_context dev) +{ + assert(dev != NULL); + + if (rn2903_command(dev, "mac get appeui")) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t rn2903_set_application_key(const rn2903_context dev, + const char *str) +{ + assert(dev != NULL); + assert(str != NULL); + + // first verify that the string is a valid hex string of the right + // size (32 bytes for the 16 byte key) + + if (!validate_hex_str(str) || strlen(str) != 32) + { + printf("%s: invalid 32 byte application key hex string.\n", + __FUNCTION__); + return UPM_ERROR_OPERATION_FAILED; + } + + if (rn2903_command_with_arg(dev, "mac set appkey", str)) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t rn2903_get_application_key(const rn2903_context dev) +{ + assert(dev != NULL); + + if (rn2903_command(dev, "mac get appkey")) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t rn2903_set_device_addr(const rn2903_context dev, + const char *str) +{ + assert(dev != NULL); + assert(str != NULL); + + // first verify that the string is a valid hex string of the right + // size (8 bytes for the 4 byte key) + + if (!validate_hex_str(str) || strlen(str) != 8) + { + printf("%s: invalid 8 byte device addr hex string.\n", + __FUNCTION__); + return UPM_ERROR_OPERATION_FAILED; + } + + if (rn2903_command_with_arg(dev, "mac set devaddr", str)) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t rn2903_get_device_addr(const rn2903_context dev) +{ + assert(dev != NULL); + + if (rn2903_command(dev, "mac get devaddr")) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t rn2903_set_network_session_key(const rn2903_context dev, + const char *str) +{ + assert(dev != NULL); + assert(str != NULL); + + // first verify that the string is a valid hex string of the right + // size (32 bytes for the 16 byte key) + + if (!validate_hex_str(str) || strlen(str) != 32) + { + printf("%s: invalid 32 byte network session key hex string.\n", + __FUNCTION__); + return UPM_ERROR_OPERATION_FAILED; + } + + if (rn2903_command_with_arg(dev, "mac set nwkskey", str)) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t rn2903_set_application_session_key(const rn2903_context dev, + const char *str) +{ + assert(dev != NULL); + assert(str != NULL); + + // first verify that the string is a valid hex string of the right + // size (32 bytes for the 16 byte key) + + if (!validate_hex_str(str) || strlen(str) != 32) + { + printf("%s: invalid 32 byte application session key hex string.\n", + __FUNCTION__); + return UPM_ERROR_OPERATION_FAILED; + } + + if (rn2903_command_with_arg(dev, "mac set appskey", str)) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t rn2903_mac_save(const rn2903_context dev) +{ + assert(dev != NULL); + + if (rn2903_command(dev, "mac save")) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t rn2903_mac_pause(const rn2903_context dev) +{ + assert(dev != NULL); + + if (rn2903_command(dev, "mac pause")) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t rn2903_mac_resume(const rn2903_context dev) +{ + assert(dev != NULL); + + if (rn2903_command(dev, "mac resume")) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +RN2903_MAC_TX_STATUS_T rn2903_mac_tx(const rn2903_context dev, + RN2903_MAC_MSG_TYPE_T type, + int port, const char *payload) +{ + assert(dev != NULL); + assert(payload != NULL); + + // check some things + + // port can only be between 1 and 223 + if (port < 1 || port > 223) + { + printf("%s: port must be between 1 and 223\n", __FUNCTION__); + return RN2903_MAC_TX_STATUS_UPM_ERROR; + } + + // make sure payload is a valid hex string + if (!validate_hex_str(payload)) + { + printf("%s: payload is not a valid hex string\n", __FUNCTION__); + return RN2903_MAC_TX_STATUS_UPM_ERROR; + } + + // get the mac status and ensure that 1) we are joined to a + // LoRaWAN network, 2) the mac status is idle, 3) we have not been + // silenced, and 4) MAC has not been paused. + + if (rn2903_update_mac_status(dev)) + { + printf("%s: rn2903_update_mac_status() failed\n", __FUNCTION__); + return RN2903_MAC_TX_STATUS_UPM_ERROR; + } + + // if the radio is not idle, we aren't going anywhere + RN2903_MAC_STATUS_T mac_status = rn2903_get_mac_status(dev); + if (mac_status != RN2903_MAC_STAT_IDLE) + return RN2903_MAC_TX_STATUS_BUSY; + + // now check the rest of the status bits... + uint16_t status = rn2903_get_mac_status_word(dev); + if (!(status & RN2903_MAC_STATUS_JOINED)) + return RN2903_MAC_TX_STATUS_NOT_JOINED; + else if (status & RN2903_MAC_STATUS_SILENT) + return RN2903_MAC_TX_STATUS_SILENT; + else if (status & RN2903_MAC_STATUS_PAUSED) + return RN2903_MAC_TX_STATUS_MAC_PAUSED; + + // good so far, build and send the command. Then we check for + // more things. + + char cmd[RN2903_CMD_BUFFER_32B] = {}; + snprintf(cmd, RN2903_CMD_BUFFER_32B, "mac tx %s %d", + (type == RN2903_MAC_MSG_TYPE_CONFIRMED) ? "cnf" : "uncnf", + port); + + RN2903_RESPONSE_T rv; + if ((rv = rn2903_command_with_arg(dev, cmd, payload))) + { + printf("%s: mac tx command failed (%d).\n", __FUNCTION__, rv); + return RN2903_MAC_TX_STATUS_UPM_ERROR; + } + + // now check for some other things we couldn't check before + if (rn2903_find(dev, "no_free_ch")) + return RN2903_MAC_TX_STATUS_NO_CHAN; + else if (rn2903_find(dev, "frame_counter_err_rejoin_needed")) + return RN2903_MAC_TX_STATUS_FC_NEED_REJOIN; + else if (rn2903_find(dev, "invalid_data_len")) + return RN2903_MAC_TX_STATUS_BAD_DATA_LEN; + + // now we wait for transmission to complete, and a possible + // downlink packet. + + if ((rv = rn2903_waitfor_response(dev, dev->cmd_resp2_wait_ms))) + { + printf("%s: mac tx second response failed (%d).\n", __FUNCTION__, rv); + return RN2903_MAC_TX_STATUS_UPM_ERROR; + } + + if (rn2903_find(dev, "mac_tx_ok")) + return RN2903_MAC_TX_STATUS_TX_OK; + else if (rn2903_find(dev, "mac_err")) + return RN2903_MAC_TX_STATUS_MAC_ERR; + else if (rn2903_find(dev, "invalid_data_len")) + return RN2903_MAC_TX_STATUS_BAD_DATA_LEN; + else if (rn2903_find(dev, "mac_rx")) + return RN2903_MAC_TX_STATUS_RX_RECEIVED; // we got a downlink + // packet in the + // response buffer + + // if it's anything else, we failed :( + printf("%s: unexpected response to mac tx command\n", __FUNCTION__); + + return RN2903_JOIN_STATUS_UPM_ERROR; +} + +RN2903_RESPONSE_T rn2903_radio_tx(const rn2903_context dev, + const char *payload) +{ + assert(dev != NULL); + assert(payload != NULL); + + // check some things + + // make sure payload is a valid hex string + if (!validate_hex_str(payload)) + { + printf("%s: payload is not a valid hex string\n", __FUNCTION__); + return RN2903_MAC_TX_STATUS_UPM_ERROR; + } + + // get the mac status and ensure that we are paused + if (rn2903_update_mac_status(dev)) + { + printf("%s: rn2903_update_mac_status() failed\n", __FUNCTION__); + return RN2903_MAC_TX_STATUS_UPM_ERROR; + } + + uint16_t status = rn2903_get_mac_status_word(dev); + if (!(status & RN2903_MAC_STATUS_PAUSED)) + { + printf("%s: MAC must be paused first\n", __FUNCTION__); + return RN2903_RESPONSE_UPM_ERROR; + } + + // good so far, build and send the command + + RN2903_RESPONSE_T rv; + if ((rv = rn2903_command_with_arg(dev, "radio tx", payload))) + { + printf("%s: radio tx command failed (%d).\n", __FUNCTION__, rv); + return rv; + } + + // we're done + return RN2903_RESPONSE_OK; +} + +RN2903_RESPONSE_T rn2903_radio_rx(const rn2903_context dev, + int window_size) +{ + assert(dev != NULL); + + // check some things + + // get the mac status and ensure that we are paused + if (rn2903_update_mac_status(dev)) + { + printf("%s: rn2903_update_mac_status() failed\n", __FUNCTION__); + return RN2903_MAC_TX_STATUS_UPM_ERROR; + } + + uint16_t status = rn2903_get_mac_status_word(dev); + if (!(status & RN2903_MAC_STATUS_PAUSED)) + { + printf("%s: MAC must be paused first\n", __FUNCTION__); + return RN2903_RESPONSE_UPM_ERROR; + } + + // good so far, build and send the command + + char cmd[RN2903_CMD_BUFFER_32B] = {}; + snprintf(cmd, RN2903_CMD_BUFFER_32B, "radio rx %d", window_size); + + RN2903_RESPONSE_T rv; + if ((rv = rn2903_command(dev, cmd))) + { + printf("%s: radio tx command failed (%d).\n", __FUNCTION__, rv); + return rv; + } + + // now, wait for and return the second response + return rn2903_waitfor_response(dev, dev->cmd_resp2_wait_ms); +} + +upm_result_t rn2903_mac_set_battery(const rn2903_context dev, int level) +{ + assert(dev != NULL); + + if (level < 0 || level > 255) + { + printf("%s: level must be between 0 and 255\n", __FUNCTION__); + return UPM_ERROR_OPERATION_FAILED; + } + + char cmd[RN2903_CMD_BUFFER_32B] = {}; + snprintf(cmd, RN2903_CMD_BUFFER_32B, "mac set bat %d", level); + + RN2903_RESPONSE_T rv; + if ((rv = rn2903_command(dev, cmd))) + { + printf("%s: mac tx command failed (%d).\n", __FUNCTION__, rv); + return UPM_ERROR_OPERATION_FAILED; + } + + return UPM_SUCCESS; +} + +bool rn2903_autobaud(const rn2903_context dev, int retries) +{ + assert(dev != NULL); + + do + { + // trigger rn2903 auto-baud detection + + // send a break signal, then a 0x55, then try a command + mraa_result_t rv; + if ((rv = mraa_uart_sendbreak(dev->uart, 0))) + { + // we don't want to fail here if break not implemented or + // supported + if (rv != MRAA_ERROR_FEATURE_NOT_IMPLEMENTED && + rv != MRAA_ERROR_FEATURE_NOT_SUPPORTED) + { + + printf("%s: mraa_uart_sendbreak() failed.\n", __FUNCTION__); + return UPM_ERROR_OPERATION_FAILED; + } + } + + upm_delay_ms(100); + + // The magic autobaud detection character + char buf = 0x55; + rn2903_write(dev, &buf, 1); + + upm_delay_ms(100); + + // try a command to verify speed + if (!rn2903_command(dev, "sys get ver")) + break; + + if (dev->debug) + printf("%s: RETRIES %d: FAIL!\n", __FUNCTION__, retries); + } while (retries-- > 0); + + if (retries <= 0) + return false; + + if (dev->debug) + printf("%s: RETRIES %d: success!\n", __FUNCTION__, retries); + + return true; +} + +const char *rn2903_get_radio_rx_payload(const rn2903_context dev) +{ + assert(dev != NULL); + + // first make sure we have the right data in the response buffer. + // The response buffer should contain "radio_rx" + // (note the two spaces between radio_rx and the payload.) + if (rn2903_find(dev, "radio_rx") || dev->resp_len > 10) + return &(dev->resp_data[10]); + + return NULL; +} diff --git a/src/rn2903/rn2903.cxx b/src/rn2903/rn2903.cxx new file mode 100644 index 00000000..83c5bdbb --- /dev/null +++ b/src/rn2903/rn2903.cxx @@ -0,0 +1,334 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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 +#include +#include + +#include "rn2903.hpp" + +using namespace upm; +using namespace std; + +RN2903::RN2903(unsigned int uart, unsigned int baudrate) : + m_rn2903(rn2903_init(uart, baudrate)) +{ + if (!m_rn2903) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_init() failed"); +} + +RN2903::RN2903(string uart_path, unsigned int baudrate) : + m_rn2903(rn2903_init_tty(uart_path.c_str(), baudrate)) +{ + if (!m_rn2903) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_init_tty() failed"); +} + +RN2903::~RN2903() +{ + rn2903_close(m_rn2903); +} + +std::string RN2903::read(int size) +{ + char buffer[size]; + + int rv; + + if ((rv = rn2903_read(m_rn2903, buffer, (size_t)size)) < 0) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_read() failed"); + + return string(buffer, rv); +} + +int RN2903::write(std::string buffer) +{ + int rv; + + if ((rv = rn2903_write(m_rn2903, (char*)buffer.data(), + buffer.size())) < 0) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_write() failed"); + + return rv; +} + +void RN2903::setResponseWaitTime(unsigned int wait_time) +{ + rn2903_set_response_wait_time(m_rn2903, wait_time); +} + +void RN2903::setResponse2WaitTime(unsigned int wait_time) +{ + rn2903_set_response2_wait_time(m_rn2903, wait_time); +} + +bool RN2903::dataAvailable(unsigned int millis) +{ + return rn2903_data_available(m_rn2903, millis); +} + +void RN2903::drain() +{ + rn2903_drain(m_rn2903); + return; +} + +RN2903_RESPONSE_T RN2903::command(const std::string cmd) +{ + return rn2903_command(m_rn2903, cmd.c_str()); +} + +RN2903_RESPONSE_T RN2903::commandWithArg(const std::string cmd, + const std::string arg) + +{ + return rn2903_command_with_arg(m_rn2903, cmd.c_str(), arg.c_str()); +} + +RN2903_RESPONSE_T RN2903::waitForResponse(int wait_ms) +{ + return rn2903_waitfor_response(m_rn2903, wait_ms); +} + +std::string RN2903::getResponse() +{ + return string(rn2903_get_response(m_rn2903), + rn2903_get_response_len(m_rn2903)); +} + +int RN2903::getResponseLen() +{ + return rn2903_get_response_len(m_rn2903); +} + +void RN2903::setDeviceEUI(const std::string str) +{ + if (rn2903_set_device_eui(m_rn2903, str.c_str())) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_set_device_eui() failed"); +} + +void RN2903::getDeviceEUI() +{ + if (rn2903_get_device_eui(m_rn2903)) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_get_device_eui() failed"); +} + +void RN2903::setNetworkSessionKey(const std::string str) +{ + if (rn2903_set_network_session_key(m_rn2903, str.c_str())) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_set_network_session_key() failed"); +} + +void RN2903::setApplicationSessionKey(const std::string str) +{ + if (rn2903_set_application_session_key(m_rn2903, str.c_str())) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_set_application_session_key() failed"); +} + +void RN2903::setApplicationEUI(const std::string str) +{ + if (rn2903_set_application_eui(m_rn2903, str.c_str())) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_set_application_eui() failed"); +} + +void RN2903::getApplicationEUI() +{ + if (rn2903_get_application_eui(m_rn2903)) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_get_application_eui() failed"); +} + +void RN2903::setApplicationKey(const std::string str) +{ + if (rn2903_set_application_key(m_rn2903, str.c_str())) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_set_application_key() failed"); +} + +void RN2903::getApplicationKey() +{ + if (rn2903_get_application_key(m_rn2903)) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_get_application_key() failed"); +} + +void RN2903::setDeviceAddr(const std::string str) +{ + if (rn2903_set_device_addr(m_rn2903, str.c_str())) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_set_device_addr() failed"); +} + +void RN2903::getDeviceAddr() +{ + if (rn2903_get_device_addr(m_rn2903)) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_get_device_addr() failed"); +} + +std::string RN2903::toHex(const std::string src) +{ + const char *buf = rn2903_to_hex(m_rn2903, src.c_str(), src.size()); + + if (!buf) + return string(""); + else + return string(buf); +} + +std::string RN2903::fromHex(const std::string src) +{ + const char *buf = rn2903_from_hex(m_rn2903, src.c_str()); + + if (!buf) + return string(""); + else + return string(buf); +} + +RN2903_JOIN_STATUS_T RN2903::join(RN2903_JOIN_TYPE_T type) +{ + return rn2903_join(m_rn2903, type); +} + +RN2903_MAC_TX_STATUS_T RN2903::macTx(RN2903_MAC_MSG_TYPE_T type, int port, + std::string payload) +{ + return rn2903_mac_tx(m_rn2903, type, port, payload.c_str()); +} + +RN2903_RESPONSE_T RN2903::radioTx(const std::string payload) +{ + return rn2903_radio_tx(m_rn2903, payload.c_str()); +} + +RN2903_RESPONSE_T RN2903::radioRx(int window_size) +{ + return rn2903_radio_rx(m_rn2903, window_size); +} + +std::string RN2903::getHardwareEUI() +{ + return string(rn2903_get_hardware_eui(m_rn2903)); +} + +void RN2903::updateMacStatus() +{ + if (rn2903_update_mac_status(m_rn2903)) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_update_mac_status() failed"); +} + +int RN2903::getMacStatusWord() +{ + return int(rn2903_get_mac_status_word(m_rn2903)); +} + +RN2903_MAC_STATUS_T RN2903::getMacStatus() +{ + return rn2903_get_mac_status(m_rn2903); +} + +void RN2903::macSave() +{ + if (rn2903_mac_save(m_rn2903)) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_mac_save() failed"); +} + +void RN2903::macPause() +{ + if (rn2903_mac_pause(m_rn2903)) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_mac_pause() failed"); +} + +void RN2903::macResume() +{ + if (rn2903_mac_resume(m_rn2903)) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_mac_resume() failed"); +} + +void RN2903::reset() +{ + if (rn2903_reset(m_rn2903)) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_reset() failed"); +} + +void RN2903::macSetBattery(int level) +{ + if (rn2903_mac_set_battery(m_rn2903, level)) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_mac_set_battery() failed"); +} + +void RN2903::setDebug(bool enable) +{ + rn2903_set_debug(m_rn2903, enable); +} + +void RN2903::setBaudrate(unsigned int baudrate) +{ + if (rn2903_set_baudrate(m_rn2903, baudrate)) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_set_baudrate() failed"); +} + +void RN2903::setFlowControl(RN2903_FLOW_CONTROL_T fc) +{ + if (rn2903_set_flow_control(m_rn2903, fc)) + throw std::runtime_error(string(__FUNCTION__) + + ": rn2903_set_flow_control() failed"); +} + +bool RN2903::find(const std::string str) +{ + return rn2903_find(m_rn2903, str.c_str()); +} + +std::string RN2903::getRadioRxPayload() +{ + const char *payload = rn2903_get_radio_rx_payload(m_rn2903); + + if (!payload) + return string(""); + else + return string(payload); +} + +bool RN2903::autobaud(int retries) +{ + return rn2903_autobaud(m_rn2903, retries); +} diff --git a/src/rn2903/rn2903.h b/src/rn2903/rn2903.h new file mode 100644 index 00000000..5e578026 --- /dev/null +++ b/src/rn2903/rn2903.h @@ -0,0 +1,718 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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. + */ + +#pragma once + +#include +#include +#include + +#include +#include + +#include "rn2903_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /** + * @file rn2903.h + * @library rn2903 + * @brief Generic API for the Microchip RN2903 LoRa Radio + * + */ + + /** + * Device context + */ + typedef struct _rn2903_context { + mraa_uart_context uart; + // store the baudrate + int baudrate; + + // response data buffer, stripped of CR/LF + char resp_data[RN2903_MAX_BUFFER]; + // length of response data + size_t resp_len; + + // these are allocated buffers we use manage and reuse + // internally to store conversions from/to hex and back + char *to_hex_buf; + char *from_hex_buf; + + // maximum time to wait for a response after a command is + // submitted + int cmd_resp_wait_ms; + + // maximum time to wait for a second response after a command is + // submitted + int cmd_resp2_wait_ms; + + // debugging output + bool debug; + + // our hardware hex encoded EUI + terminating NULL + char hardware_eui[RN2903_MAX_HEX_EUI64 + 1]; + + // 16b mac status word + uint16_t mac_status_word; + // this is the mac_status bitfield of the mac status word + RN2903_MAC_STATUS_T mac_mac_status; + } *rn2903_context; + + /** + * RN2903 Initializer for UART operation using a UART index + * + * @param uart Specify which uart to use + * @param baudrate Specify the baudrate to use. 57600 is the + * default baudrate of this device. + * @return an initialized device context on success, NULL on error + */ + rn2903_context rn2903_init(unsigned int uart, unsigned int baudrate); + + /** + * RN2903 Initializer for UART operation using a filesystem tty + * path (eg. /dev/ttyUSB0) + * + * @param uart_tty character string representing a filesystem path to a + * serial tty device + * @param baudrate Specify the baudrate to use. 57600 is the + * default baudrate of this device. + * @return an initialized device context on success, NULL on error + */ + rn2903_context rn2903_init_tty(const char *uart_tty, unsigned int baudrate); + + /** + * RN2903 sensor close function + * + * @param dev Device context + */ + void rn2903_close(rn2903_context dev); + + /** + * Set the default time, in milliseconds, to wait for a response + * after sending a command. All commands return at least one + * response immediately after issuing the command. This delay + * sets the maximum amount of time to wait for it. + * + * @param dev Device context + * @param wait_ms The response delay to set, in milliseconds. + */ + void rn2903_set_response_wait_time(const rn2903_context dev, + unsigned int wait_ms); + + /** + * Set the default time, in milliseconds, to wait for the second + * response data to arrive. Some commands will have a second + * response emitted after the first response. This delay sets the + * maximum amount of time to wait for it. + * + * @param dev Device context + * @param wait_ms The response delay to set, in milliseconds. + */ + void rn2903_set_response2_wait_time(const rn2903_context dev, + unsigned int wait_ms); + + /** + * Determine whether there is data available to be read. This + * function will wait up to "millis" milliseconds for data to + * become available. + * + * @param dev Device context + * @param millis The number of milliseconds to wait for data to + * become available + * @return true if data is available to be read, false otherwise + */ + bool rn2903_data_available(const rn2903_context dev, + unsigned int millis); + + /** + * Read and throw away any data currently available to be read. + * This is useful to avoid reading data that might have been the + * result of a previous command interfering with data you + * currently want to read. + * + * @param dev Device context + */ + void rn2903_drain(const rn2903_context dev); + + /** + * Send a command, wait for a response using + * rn2903_waitfor_response(), and return the status. The response + * itself will be stored internally, and can be retrieved using + * rm2903_get_response() and rn2903_get_response_len(). Every + * command will return at least one response, and this function + * will always wait for it and store it after sending the command. + * + * @param dev Device context + * @param cmd A character string containing the command to + * send + * @return One of the RN2903_RESPONSE_T values + */ + RN2903_RESPONSE_T rn2903_command(const rn2903_context dev, + const char *cmd); + + /** + * Build a command string with the supplied command and string + * argument. Then call rn2903_command() with the result, and + * return the result. This is just a convenience function. + * + * @param dev Device context + * @param cmd A character string containing the command to + * send + * @param arg A string argument for the command + * @return One of the RN2903_RESPONSE_T values + */ + RN2903_RESPONSE_T rn2903_command_with_arg(const rn2903_context dev, + const char *cmd, + const char *arg); + + /** + * Wait up to wait_ms milliseconds for a response. + * + * In the case of errors ("invalid_param" received, timeout + * occurred, or other UPM specific error), this will be indicated + * by the return value. + * + * Otherwise, an "ok" or other data value will not be considered + * an error and will return RN2903_RESPONSE_OK. The response + * itself will be stored internally, and can be retrieved using + * rm2903_get_response() and rn2903_get_response_len(). + * + * NOTE: the response buffer is overwritten whenever this function + * is called, so if there is data in there that you need to save, + * copy it somewhere else before calling any other functions in + * this driver to be safe. + * + * @param dev Device context + * @param wait_ms The maximum number of milliseconds to wait for a + * response. + * @return One of the RN2903_RESPONSE_T values + */ + RN2903_RESPONSE_T rn2903_waitfor_response(const rn2903_context dev, + int wait_ms); + + /** + * Return a pointer to a string containing the last response. If + * you wish to save the response for later, you will need to copy + * it somewhere before calling rn2903_command(), + * rn2903_command_with_arg() or rn2903_waitfor_response(), as + * these functions will overwrite the internally stored response + * buffer each time they are called. + * + * @param dev Device context + * @return A const pointer to a string containing the last response. + */ + const char *rn2903_get_response(const rn2903_context dev); + + /** + * Return the length in bytes of the string containing the last + * response. If you wish to save the response length for later, + * you will need to copy it somewhere before calling + * rn2903_command() or rn2903_waitfor_respnse(), as these + * functions will overwrite the internally stored response length + * each time they are called. + * + * @param dev Device context + * @return The length of the last response in bytes + */ + size_t rn2903_get_response_len(const rn2903_context dev); + + /** + * Set the MAC device EUI for LoRaWAN communications. The device + * EUI must be a hex encoded string of 16 bytes. This value must + * be set for LoRaWAN OTAA joining. + * + * @param dev Device context + * @param str The 16-byte hex encoded device EUI + * @return UPM result + */ + upm_result_t rn2903_set_device_eui(const rn2903_context dev, + const char *str); + + /** + * Retrieve the device EUI from the device. If this function + * succeeds, you can then use rn2903_get_response() to get the + * value. + * + * @param dev Device context + * @return UPM result + */ + upm_result_t rn2903_get_device_eui(const rn2903_context dev); + + /** + * Set the MAC application EUI for LoRaWAN communications. The + * application EUI must be a hex encoded string of 16 bytes. This + * value must be set for LoRaWAN OTAA joining. + * + * @param dev Device context + * @param str The 16-byte hex encoded application EUI + * @return UPM result + */ + upm_result_t rn2903_set_application_eui(const rn2903_context dev, + const char *str); + + /** + * Retrieve the application EUI from the device. If this function + * succeeds, you can then use rn2903_get_response() to get the + * value. + * + * @param dev Device context + * @return UPM result + */ + upm_result_t rn2903_get_application_eui(const rn2903_context dev); + + /** + * Set the MAC application key for LoRaWAN communications. The + * application key must be a hex encoded string of 32 bytes. This + * value must be set for LoRaWAN OTAA joining. + * + * @param dev Device context + * @param str The 32-byte hex encoded application key + * @return UPM result + */ + upm_result_t rn2903_set_application_key(const rn2903_context dev, + const char *str); + + /** + * Retrieve the application key from the device. If this function + * succeeds, you can then use rn2903_get_response() to get the + * value. + * + * @param dev Device context + * @return UPM result + */ + upm_result_t rn2903_get_application_key(const rn2903_context dev); + + /** + * Set the MAC device address for LoRaWAN communications. The + * device address must be a hex encoded string of 8 bytes. This + * value must be set for LoRaWAN ABP joining. + * + * For OTAA joining, this value will be overwritten once the join + * has completed, and therefore must not be set if performing an + * OTAA join. + * + * @param dev Device context + * @param str The 8-byte hex encoded device address + * @return UPM result + */ + upm_result_t rn2903_set_device_addr(const rn2903_context dev, + const char *str); + + /** + * Retrieve the device address from the device. If this function + * succeeds, you can then use rn2903_get_response() to get the + * value. + * + * @param dev Device context + * @return UPM result + */ + upm_result_t rn2903_get_device_addr(const rn2903_context dev); + + /** + * Set the MAC network session key for LoRaWAN communications. + * The network session key must be a hex encoded string of 32 + * bytes. This value must be set for LoRaWAN ABP joining. It it + * not possible to retrieve this key. + * + * For OTAA joining, this value will be overwritten once the join + * has completed, and therefore must not be set if performing an + * OTAA join. + * + * @param dev Device context + * @param str The 32-byte hex encoded network session key + * @return UPM result + */ + upm_result_t rn2903_set_network_session_key(const rn2903_context dev, + const char *str); + + /** + * Set the MAC application session key for LoRaWAN communications. + * The application session key must be a hex encoded string of 32 + * bytes. This value must be set for LoRaWAN ABP joining. It it + * not possible to retrieve this key. + * + * For OTAA joining, this value will be overwritten once the join + * has completed, and therefore must not be set if performing an + * OTAA join. + * + * @param dev Device context + * @param str The 32-byte hex encoded application session key + * @return UPM result + */ + upm_result_t rn2903_set_application_session_key(const rn2903_context dev, + const char *str); + + /** + * Convert src into a hex byte string. All non-command related + * data such as keys, and payload sent to the device must be hex + * encoded. The buffers used for this conversion are managed + * internally, so do not call free() on the returned pointer. If + * you need to keep a copy of the resulting hex string, copy it + * somewhere before calling this function again. + * + * @param dev Device context + * @param src A char pointer pointing to the byte array to encode + * @param len The length in bytes of the data to encode + * @return A const pointer to the resulting hex string, or NULL if + * there was an error. + */ + const char *rn2903_to_hex(const rn2903_context dev, + const char *src, int len); + + /** + * Convert a hex byte string into an array of bytes. The hex + * string must have a length (not including the 0 terminator) that + * is a multiple of two, and all characters in the string must be + * valid hex characters. The buffers used for this conversion are + * managed internally, so do not call free() on the returned + * pointer. If you need to keep a copy of the resulting data byte + * array, copy it somewhere before calling this function again. + * The length of the returned data will be the length of the hex + * source string divided by 2, with a 0 byte terminator at the end + * in case a text string is being decoded. + * + * @param dev Device context + * @return A const pointer to the resulting data string, or NULL if + * there was an error + */ + const char *rn2903_from_hex(const rn2903_context dev, + const char *src); + + /** + * Join a LoRaWAN network. There are two types of joins possible + * - Over The Air Activation (OTAA) and Activation by + * Personalization (ABP). Each join method requires that certain + * items have been configured first. + * + * For OTAA activation, the Device Extended Unique Identifier + * (EUI), the Application EUI, and Application Key must be set. + * + * For ABP activation, The Device Address, Network Session Key, + * and the Application Session Key must be set. + * + * @param dev Device context + * @param type The LoRaWAN activation type, one of the + * RN2903_JOIN_TYPE_T values + * @return The status of the join, one of the RN2903_JOIN_STATUS_T + * values + */ + RN2903_JOIN_STATUS_T rn2903_join(const rn2903_context dev, + RN2903_JOIN_TYPE_T type); + + /** + * Transmit a packet in a LoRaWAN network. You must + * already be joined to a LoRaWAN network for this command to + * succeed, and the MAC stack must be in the idle + * (RN2903_MAC_STAT_IDLE) state. + * + * The packet payload must be a valid 0-terminated hex encoded + * string. + * + * There is the possibility of receiving a downlink message after + * transmitting a packet. If this occurs, this function will + * return RN2903_MAC_TX_STATUS_RX_RECEIVED, and the returned data + * will be stored in the response buffer. NOTE: calling pretty + * much any function that issues commands to the device will + * overwrite the response buffer, so save a copy of it if you need + * it before calling other functions. + * + * @param dev Device context + * @param type The type of message to send - confirmed or + * unconfirmed. One of the RN2903_MAC_MSG_TYPE_T values. + * @param port An integer in the range 1-223 + * @param payload A 0-terminated, hex encoded string that makes up + * the payload of the message + * @return The status of the transmit request, one of the + * RN2903_MAC_TX_STATUS_T values + */ + RN2903_MAC_TX_STATUS_T rn2903_mac_tx(const rn2903_context dev, + RN2903_MAC_MSG_TYPE_T type, + int port, const char *payload); + + /** + * Transmit a packet. This method uses the radio directly without + * the LoRaWAN stack running. For this reason, you must call + * rn2903_mac_pause() before trying to transmit using this + * function. You should also configure any parameters (frequency, + * etc), before calling this function. + * + * @param dev Device context + * @param payload A 0-terminated, hex encoded string that makes up + * the payload of the message + * @return The status of the transmit request, one of the + * RN2903_RESPONSE_T values + */ + RN2903_RESPONSE_T rn2903_radio_tx(const rn2903_context dev, + const char *payload); + + /** + * Receive a packet. This method uses the radio directly without + * the LoRaWAN stack running. For this reason, you must call + * rn2903_mac_pause() before trying to receive using this + * function. You should also configure any parameters (frequency, + * etc) to match the transmitter before calling this function. + * + * @param dev Device context + * @param window_size An integer that represents the number of + * symbols to wait for (lora) or the maximum number of + * milliseconds to wait (fsk). This parameter is passed to the "radio + * rx" command. Passing 0 causes the radio to enter continuous + * receive mode which will return when either a packet is + * received, or the radio watchdog timer expires. See the RN2903 + * Command Reference for details. + * @return The status of the transmit request, one of the + * RN2903_RESPONSE_T values + */ + RN2903_RESPONSE_T rn2903_radio_rx(const rn2903_context dev, + int window_size); + + /** + * Return the Hardware Extended Unique Identifier. The is a 16 + * byte hex encoded string representing the 64b hardware EUI. + * This value cannot be changed, and is globally unique to each + * device. It is obtained from the device at initialization time. + * + * @param dev Device context + * @return A const string pointer to the hex encoded Hardware EUI + */ + const char *rn2903_get_hardware_eui(const rn2903_context dev); + + /** + * Retrieve the device MAC status, decode it, and store it + * internally. This function must be called prior to calling + * rn2903_get_mac_status_word() or rn2903_get_mac_status(). + * + * @param dev Device context + * @return UPM result + */ + upm_result_t rn2903_update_mac_status(const rn2903_context dev); + + /** + * Retrieve the MAC status word. rn2903_update_mac_status() must + * have been called prior to calling this function. + * + * @param dev Device context + * @return The MAC status word. This is a bitmask of + * RN2903_MAC_STATUS_BITS_T bits. + */ + uint16_t rn2903_get_mac_status_word(const rn2903_context dev); + + /** + * Retrieve the MAC status. rn2903_update_mac_status() must have + * been called prior to calling this function. The MAC status is + * a bitfield embedded in the mac_status_word. It provides + * information on the status of the internal MAC state machine. + * + * @param dev Device context + * @return The MAC status, one of the RN2903_MAC_STATUS_T values. + */ + RN2903_MAC_STATUS_T rn2903_get_mac_status(const rn2903_context dev); + + /** + * Save the configurable device values to EEPROM. These values + * will be reloaded automatically on a reset or power up. + * + * The data that can be saved are: deveui, appeui, appkey, nwkskey + * (Network Session Key), appskey, devaddr, channels, upctr + * (Uplink Counter), dnctr (Downlink Counter), adr (automatic + * data-rate) state, and rx2 (the RX2 receive window parameters). + * + * @param dev Device context + * @return UPM result + */ + upm_result_t rn2903_mac_save(const rn2903_context dev); + + /** + * Pause the MAC LoRaWAN stack. This device can operate in one of + * two modes. + * + * The most common mode is used to join and participate in a LoRaWAN + * network as a Class A End Device. This is handled by the MAC + * LoRaWAN stack on the device dealing with the details of + * LoRaWAN participation automatically. + * + * The other mode disables MAC LoRaWAN stack functionality and + * allows you to issue commands directly to the radio to set + * frequencies, data rates, modulation and many other parameters. + * + * Calling this function disables the MAC LoRaWAN stack and allows + * you to issue radio commands that are otherwise handled + * automatically. + * + * When pausing, the maximum allowable pause time in milliseconds + * will be returned in the response buffer. You can grab this + * value by calling rn2903_get_response() after this function + * completes successfully. + * + * When the MAC is idle (rn2903_get_mac_status()), you can pause + * indefinitely. + * + * @param dev Device context + * @return UPM result + */ + upm_result_t rn2903_mac_pause(const rn2903_context dev); + + /** + * Resume the MAC LoRaWAN stack. Call this to resume MAC LoRaWAN + * operation after having called rn2903_mac_pause(), to resume + * participation in a LoRaWAN network. + * + * @param dev Device context + * @return UPM result + */ + upm_result_t rn2903_mac_resume(const rn2903_context dev); + + /** + * Reset the device. Any configuration is lost, as well as + * the current join status. This method also calls + * rn2903_set_baudrate() after the reset to re-establish + * communications with the device in the event you are not + * using the default baudrate (which the device will revert to + * after a reset). + * + * @param dev Device context + * @return UPM result + */ + upm_result_t rn2903_reset(const rn2903_context dev); + + /** + * LoRaWAN communications allows for the reporting of current + * battery charge remaining to the LoRaWAN gateway/network server. + * This function allows you to specify the value that should be + * reported. + * + * The valid values are from 0 to 255. + * 0 = using external power + * 1(low) to 254(high) = battery power + * 255 = unable to measure battery level + * + * @param dev Device context + * @param level The battery level value from 0-255 + * @return UPM result + */ + upm_result_t rn2903_mac_set_battery(const rn2903_context dev, int level); + + /** + * Enable debugging. If enabled, commands will be printed out + * before being sent to the device. Any responses will be printed + * out after retrieval. Other miscellaneous debug output will + * also be printed. + * + * @param dev Device context + * @param enable true to enable debugging, false otherwise + */ + void rn2903_set_debug(const rn2903_context dev, bool enable); + + /** + * Read character data from the device + * + * @param dev Device context + * @param buffer The character buffer to read data into + * @param len The maximum size of the buffer + * @return The number of bytes successfully read, or -1 on error + */ + int rn2903_read(const rn2903_context dev, char *buffer, size_t len); + + /** + * Write character data to the device + * + * @param dev Device context + * @param buffer The character buffer containing data to write + * @param len The number of bytes to write + * @return The number of bytes successfully written, or -1 on error + */ + int rn2903_write(const rn2903_context dev, const char *buffer, size_t len); + + /** + * Set the baudrate of the device. Auto-bauding is currently only + * supported on Linux (due to the need to send a break signal) and + * only on a recent MRAA which supports it (> 1.6.1). If on a + * non-linux OS, you should not try to change the baudrate to + * anything other than the default 57600 or you will lose control + * of the device. + * + * @param dev Device context + * @param baudrate The baud rate to set for the device + * @return UPM result + */ + upm_result_t rn2903_set_baudrate(const rn2903_context dev, + unsigned int baudrate); + + /** + * Set a flow control method for the UART. By default, during + * initialization, flow control is disabled. The device MAY + * support hardware flow control, but MRAA does not (at least for + * UART numbers), so we can't either. We leave the option here + * though so that if you are using a TTY (as opposed to a UART + * instance) it might work if the device is also configured to use + * hardware flow control. + * + * @param dev Device context + * @param fc One of the RN2903_FLOW_CONTROL_T values + * @return the UPM result + */ + upm_result_t rn2903_set_flow_control(const rn2903_context dev, + RN2903_FLOW_CONTROL_T fc); + + /** + * This is a utility function that can be used to indicate if a + * given string is present at the beginning of the response + * buffer. The search is case sensitive. + * + * @param dev Device context + * @param str The 0 teminated string to search for + * @return true if the string was found at the beginning of the + * response buffer, false otherwise + */ + bool rn2903_find(const rn2903_context dev, const char *str); + + /** + * This is a utility function that can be used to return a pointer + * to the location in the response buffer where the hex encoded + * payload starts for radio_rx messages received. + * + * @param dev Device context + * @return A pointer to the start of the hex payload in the + * response buffer, or NULL if the response buffer does not + * contain a radio_rx sentence. + */ + const char *rn2903_get_radio_rx_payload(const rn2903_context dev); + + /** + * This function attempts to sync the device to the current + * baudrate. It tries retries times, to send an autobaud + * sequence to the device and run a test command. + * + * @param dev Device context + * @param retries The number of times to retry autobaud detection + * @return true if the test command succeeded, false otherwise + */ + bool rn2903_autobaud(const rn2903_context dev, int retries); + +#ifdef __cplusplus +} +#endif diff --git a/src/rn2903/rn2903.hpp b/src/rn2903/rn2903.hpp new file mode 100644 index 00000000..5fac702e --- /dev/null +++ b/src/rn2903/rn2903.hpp @@ -0,0 +1,636 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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. + */ +#pragma once + +#include +#include + +#include "rn2903.h" + +namespace upm { + /** + * @brief API for the Microchip RN2903 LoRa radio + * @defgroup rn2903 libupm-rn2903 + * @ingroup uart wifi + */ + + /** + * @library rn2903 + * @sensor rn2903 + * @comname Microchip RN2903 LoRa radio + * @type wifi + * @con uart + * @snippet rn2903.cxx Interesting + * @snippet rn2903-p2p-tx.cxx Interesting + * @snippet rn2903-p2p-rx.cxx Interesting + * + * @brief API for the Microchip RN2903 LoRa radio + * + * Microchip’s RN2903 Low-Power Long Range LoRa Technology + * Transceiver module provides an easy to use, low-power solution + * for long range wireless data transmission. The advanced command + * interface offers rapid time to market. + * + * The RN2903 module complies with the LoRaWAN Class A protocol + * specifications. It integrates RF, a baseband controller, and a + * command Application Programming Interface (API) processor, + * making it a complete long range solution. + * + * Most of the effort in this driver has been geared to supporting + * operation within a LoRaWAN network, however methods are + * provided to allow you to control the device directly so that + * you can implement whatever solution you require. + */ + + class RN2903 { + public: + + /** + * RN2903 object constructor for a UART specified by MRAA number. + * + * @param uart Specify which uart to use. + * @param baudrate Specify the baudrate to use. The default + * is 57600. + */ + RN2903(unsigned int uart, + unsigned int baudrate=RN2903_DEFAULT_BAUDRATE); + + /** + * RN2903 object constructor for a UART specified by PATH (ex: + * /dev/ttyUSB0) + * + * @param uart_path Specify path of UART device. + * @param baudrate Specify the baudrate to use. The default + * is 57600. + */ + RN2903(std::string uart_path, + unsigned int baudrate=RN2903_DEFAULT_BAUDRATE); + + /** + * RN2903 object destructor + */ + virtual ~RN2903(); + + /** + * Set the default time, in milliseconds, to wait for a response + * after sending a command. All commands return at least one + * response immediately after issuing the command. This delay + * sets the maximum amount of time to wait for it. + * + * @param wait_ms The response delay to set, in milliseconds. + * The default is 1 second (1000ms). + */ + void setResponseWaitTime( + unsigned int wait_ms=RN2903_DEFAULT_RESP_DELAY); + + /** + * Set the default time, in milliseconds, to wait for the second + * response data to arrive. Some commands will have a second + * response emitted after the first response. This delay sets the + * maximum amount of time to wait for it. + * + * @param wait_ms The response delay to set, in milliseconds. + * The default is 60 seconds (60000ms). + */ + void setResponse2WaitTime( + unsigned int wait_ms=RN2903_DEFAULT_RESP2_DELAY); + + /** + * Determine whether there is data available to be read. This + * function will wait up to "millis" milliseconds for data to + * become available. + * + * @param millis The number of milliseconds to wait for data to + * become available. + * @return true if data is available to be read, false otherwise. + */ + bool dataAvailable(unsigned int millis); + + /** + * Read and throw away any data currently available to be + * read. This is useful to avoid reading data that might have + * been the result of a previous command interfering with data + * you currently want to read. + */ + void drain(); + + /** + * Send a command, wait for a response using + * waitForResponse(), and return the status. The response + * itself will be stored internally, and can be retrieved + * using getResponse() and getResponseLen(). Every command + * will return at least one response, and this function will + * always wait for it and store it into the internal response + * buffer after sending the command. + * + * @param cmd A character string containing the command to + * send + * @return One of the RN2903_RESPONSE_T values + */ + RN2903_RESPONSE_T command(const std::string cmd); + + /** + * Build a command string with the supplied command and string + * argument. Then call command() with the result, and + * return the result. This is just a convenience function. + * + * @param cmd A character string containing the command to + * send + * @param arg A string argument for the command + * @return One of the RN2903_RESPONSE_T values + */ + RN2903_RESPONSE_T commandWithArg(const std::string cmd, + const std::string arg); + + /** + * Wait up to wait_ms milliseconds for a response. + * + * In the case of errors ("invalid_param" received, timeout + * occurred, or other UPM specific error), this will be indicated + * by the return value. + * + * Otherwise, an "ok" or other data value will not be considered + * an error and will return RN2903_RESPONSE_OK. The response + * itself will be stored internally, and can be retrieved using + * getResponse() and getResponseLen(). + * + * NOTE: the response buffer is overwritten whenever this function + * is called, so if there is data in there that you need to save, + * copy it somewhere else before calling any other methods in + * this driver to be safe. + * + * @param wait_ms The maximum number of milliseconds to wait for a + * response. + * @return One of the RN2903_RESPONSE_T values + */ + RN2903_RESPONSE_T waitForResponse(int wait_ms); + + /** + * Return a string containing a copy of the last response + * saved in the response buffer + * + * @return A string containing a copy of the response buffer + */ + std::string getResponse(); + + /** + * Return the length in bytes of the string containing the + * last response. + * + * @return The length of the last response in bytes + */ + int getResponseLen(); + + /** + * Set the MAC device EUI for LoRaWAN communications. The device + * EUI must be a hex encoded string of 16 bytes. This value must + * be set for LoRaWAN OTAA joining. + * + * @param str The 16-byte hex encoded device EUI + * @throws std::runtime_error if the EUI is invalid or the mac + * set command failed + */ + void setDeviceEUI(const std::string str); + + /** + * Retrieve the device EUI from the device. If this function + * succeeds, you can then use getResponse() to get the value. + * + * @return UPM result + * @throws std::runtime_error if the mac get command failed + */ + void getDeviceEUI(); + + /** + * Set the MAC device address for LoRaWAN communications. The + * device address must be a hex encoded string of 8 bytes. This + * value must be set for LoRaWAN ABP joining. + * + * For OTAA joining, this value will be overwritten once the join + * has completed, and therefore must not be set if performing an + * OTAA join. + * + * @param str The 8-byte hex encoded device address + * @throws std::runtime_error if the mac set command failed or + * the hex string is invalid + */ + void setDeviceAddr(std::string str); + + /** + * Retrieve the device address from the device. If this + * function succeeds, you can then use getResponse() to get + * the value. + * + * @throws std::runtime_error if the mac get failed + */ + void getDeviceAddr(); + + /** + * Set the MAC network session key for LoRaWAN communications. + * The network session key must be a hex encoded string of 32 + * bytes. This value must be set for LoRaWAN ABP joining. It it + * not possible to retrieve this key. + * + * For OTAA joining, this value will be overwritten once the join + * has completed, and therefore must not be set if performing an + * OTAA join. + * + * @param str The 32-byte hex encoded network session key + * @throws std::runtime_error if the mac set command failed or + * the hex string is invalid + */ + void setNetworkSessionKey(std::string str); + + /** + * Set the MAC application session key for LoRaWAN communications. + * The application session key must be a hex encoded string of 32 + * bytes. This value must be set for LoRaWAN ABP joining. It it + * not possible to retrieve this key. + * + * For OTAA joining, this value will be overwritten once the join + * has completed, and therefore must not be set if performing an + * OTAA join. + * + * @param str The 32-byte hex encoded application session key + * @throws std::runtime_error if the mac set command failed or + * the hex string is invalid + */ + void setApplicationSessionKey(std::string str); + + /** + * Set the MAC application EUI for LoRaWAN communications. The + * application EUI must be a hex encoded string of 16 bytes. This + * value must be set for LoRaWAN OTAA joining. + * + * @param str The 16-byte hex encoded application EUI + * @throws std::runtime_error if the EUI is invalid, or if the + * mac set command failed + */ + void setApplicationEUI(const std::string str); + + /** + * Retrieve the application EUI from the device. If this + * function succeeds, you can then use getResponse() to get + * the value. + * + * @throws std::runtime_error if the mac get command failed + */ + void getApplicationEUI(); + + /** + * Set the MAC application key for LoRaWAN communications. + * The application key must be a hex encoded string of 32 + * bytes. This value must be set for LoRaWAN OTAA joining. + * + * @param str The 32-byte hex encoded application key + * @throws std::runtime_error if the key is invalid, or if the + * mac set command failed + */ + void setApplicationKey(const std::string str); + + /** + * Retrieve the application key from the device. If this function + * succeeds, you can then use rn2903_get_response() to get the + * value. + * + * @throws std::runtime_error if the mac get command failed + */ + void getApplicationKey(); + + /** + * Convert src into a hex byte string. All non-command + * related data such as keys, and payload sent to the device + * must be hex encoded. + * + * @param src A string to encode + * @return A string containing the hex encoded version of str. + * In the event of an error, the return string will be empty. + */ + std::string toHex(const std::string src); + + /** + * Decode a hex byte string into the original string. The hex + * string must have a length that is a multiple of two, and + * all characters in the string must be valid hex characters. + * + * @return A string containing the decoded contents of the hex + * string passed, or an empty string if there was an error. + */ + std::string fromHex(const std::string src); + + /** + * Join a LoRaWAN network. There are two types of joins + * possible - Over The Air Activation (OTAA) and Activation by + * Personalization (ABP). Each join method requires that + * certain items have been configured first. + * + * For OTAA activation, the Device Extended Unique Identifier + * (EUI), the Application EUI, and Application Key must be + * set. + * + * For ABP activation, The Device Address, Network Session + * Key, and the Application Session Key must be set. + * + * @param type The LoRaWAN join activation type, one of the + * RN2903_JOIN_TYPE_T values + * @return The status of the join, one of the RN2903_JOIN_STATUS_T + * values + */ + RN2903_JOIN_STATUS_T join(RN2903_JOIN_TYPE_T type); + + /** + * Transmit a packet in a LoRaWAN network. You must + * already be joined to a LoRaWAN network for this command to + * succeed, and the MAC stack must be in the idle + * (RN2903_MAC_STAT_IDLE) state. + * + * The packet payload must be a valid hex encoded string. + * + * There is the possibility of receiving a downlink message after + * transmitting a packet. If this occurs, this function will + * return RN2903_MAC_TX_STATUS_RX_RECEIVED, and the returned data + * will be stored in the response buffer. + * + * @param type The type of message to send - confirmed or + * unconfirmed. One of the RN2903_MAC_MSG_TYPE_T values. + * @param port An integer in the range 1-223 + * @param payload A valid hex encoded string that makes up the + * payload of the message + * @return The status of the transmit request, one of the + * RN2903_MAC_TX_STATUS_T values + */ + RN2903_MAC_TX_STATUS_T macTx(RN2903_MAC_MSG_TYPE_T type, + int port, std::string payload); + + /** + * Transmit a packet. This method uses the radio directly + * without the LoRaWAN stack running. For this reason, you + * must call macPause() before trying to transmit using this + * function. You should also configure any radio parameters + * (frequency, etc), before calling this function. + * + * @param payload A valid hex encoded string that makes up + * the payload of the message + * @return The status of the transmit request, one of the + * RN2903_RESPONSE_T values + */ + RN2903_RESPONSE_T radioTx(const std::string payload); + + /** + * Receive a packet. This method uses the radio directly + * without the LoRaWAN stack running. For this reason, you + * must call macPause() before trying to receive using this + * function. You should also configure any parameters + * (frequency, etc) to match the transmitter before calling + * this function. + * + * @param window_size An integer that represents the number of + * symbols to wait for (lora) or the maximum number of + * milliseconds to wait (fsk). This parameter is passed to + * the "radio rx" command. Passing 0 causes the radio to + * enter continuous receive mode which will return when either + * a packet is received, or the radio watchdog timer expires. + * See the RN2903 Command Reference for details. + * @return The status of the transmit request, one of the + * RN2903_RESPONSE_T values + */ + RN2903_RESPONSE_T radioRx(int window_size); + + /** + * Return the Hardware Extended Unique Identifier (EUI). The + * is a 16 byte hex encoded string representing the 64b + * hardware EUI. This value cannot be changed, and is + * globally unique to each device. It is obtained from the + * device at initialization time. + * + * @return A const string pointer to the hex encoded Hardware EUI + */ + std::string getHardwareEUI(); + + /** + * Retrieve the device MAC status, decode it, and store it + * internally. This function must be called prior to calling + * getMacStatusWord() or getMacStatus(). + * + * @throws std::runtime_error if the mac get command failed + */ + void updateMacStatus(); + + /** + * Retrieve the MAC status word. updateMacStatus() must have + * been called prior to calling this function. + * + * @return The MAC status word. This is a bitmask of + * RN2903_MAC_STATUS_BITS_T bits. + */ + int getMacStatusWord(); + + /** + * Retrieve the MAC status. updateMacStatus() must have been + * called prior to calling this function. The MAC status is a + * bitfield embedded in the mac status word. It provides + * information on the status of the internal MAC state + * machine. + * + * @return The MAC status, one of the RN2903_MAC_STATUS_T values. + */ + RN2903_MAC_STATUS_T getMacStatus(); + + /** + * Save the configurable device values to EEPROM. These + * values will be reloaded automatically on a reset or power + * up. + * + * The data that can be saved are: deveui, appeui, appkey, + * nwkskey (Network Session Key), appskey, devaddr, channels, + * upctr (Uplink Counter), dnctr (Downlink Counter), adr + * (automatic data-rate) state, and rx2 (the RX2 receive + * window parameters). + * + * @throws std::runtime_error if the mac save command failed + */ + void macSave(); + + /** + * Pause the MAC LoRaWAN stack. This device can operate in + * one of two modes. + * + * The most common mode is used to join and participate in a + * LoRaWAN network as a Class A End Device. This is handled + * by the MAC LoRaWAN stack on the device dealing with the + * details of LoRaWAN participation automatically. + * + * The other mode disables MAC LoRaWAN stack functionality and + * allows you to issue commands directly to the radio to set + * frequencies, data rates, modulation and many other + * parameters. + * + * Calling this function disables the MAC LoRaWAN stack and + * allows you to issue radio commands that are otherwise + * handled automatically. + * + * When pausing, the maximum allowable pause time in + * milliseconds will be returned in the response buffer. You + * can grab this value by calling getResponse() after this + * function completes successfully. + * + * When the MAC is idle (getMacStatus()), you can pause + * the stack indefinitely. + * + * @throws std::runtime_error if the mac pause command failed + */ + void macPause(); + + /** + * Resume the MAC LoRaWAN stack. Call this to resume MAC + * LoRaWAN operation after having called macPause(), to resume + * participation in a LoRaWAN network. + * + * @param dev Device context + * @return UPM result + * @throws std::runtime_error if the mac resume command failed + */ + void macResume(); + + /** + * Reset the device. Any configuration is lost, as well as + * the current join status. This method also calls + * setBaudrate() after the reset to re-establish + * communications with the device in the event you are not + * using the default baudrate (which the device will revert to + * after a reset). + * + * @throws std::runtime_error if the mac reset, or + * setBaudrate() command fails + */ + void reset(); + + /** + * LoRaWAN communications allows for the reporting of current + * battery charge remaining to the LoRaWAN gateway/network server. + * This function allows you to specify the value that should be + * reported. + * + * The valid values are from 0 to 255. + * 0 = using external power + * 1(low) to 254(high) = battery power + * 255 = unable to measure battery level + * + * @param level The battery level value from 0-255 + * @throws std::runtime_error if the mac set bat command + * failed, or if the battery level is invalid + */ + void macSetBattery(int level); + + /** + * Enable debugging. If enabled, commands will be printed out + * before being sent to the device. Any responses will be printed + * out after retrieval. Other miscellaneous debug output will + * also be printed. + * + * @param enable true to enable debugging, false otherwise + */ + void setDebug(bool enable); + + /** + * Set the baudrate of the device. Auto-bauding is currently only + * supported on Linux (due to the need to send a break signal) and + * only on a recent MRAA which supports it (> 1.6.1). If on a + * non-linux OS, you should not try to change the baudrate to + * anything other than the default 57600 or you will lose control + * of the device. + * + * @param baudrate The baud rate to set for the device + * @throws std::runtime_error if the autobaud test command failed + */ + void setBaudrate(unsigned int baudrate); + + /** + * Set a flow control method for the UART. By default, during + * initialization, flow control is disabled. The device MAY + * support hardware flow control, but MRAA does not (at least for + * UART numbers), so we can't either. We leave the option here + * though so that if you are using a TTY (as opposed to a UART + * instance) it might work if the device is also configured to use + * hardware flow control. + * + * @param fc One of the RN2903_FLOW_CONTROL_T values + * @throws std::runtime_error on failure + */ + void setFlowControl(RN2903_FLOW_CONTROL_T fc); + + /** + * This is a utility function that can be used to indicate if a + * given string is present at the beginning of the response + * buffer. The search is case sensitive. + * + * @param str The string to search for + * @return true if the string was found at the beginning of the + * response buffer, false otherwise + */ + bool find(const std::string str); + + /** + * This is a utility function that can be used to return the + * hex encoded payload string for radio_rx messages received. + * + * @return A string representing the hex encoded payload, or + * an empty string if there was an error + */ + std::string getRadioRxPayload(); + + /** + * This function attempts to sync the device to the current + * baudrate. It tries retries times, to send an autobaud + * sequence to the device and run a test command. + * + * @param retries The number of times to retry autobaud detection + * @return true if the test command succeeded, false otherwise + */ + bool autobaud(int retries); + + + protected: + // rn2903 device context + rn2903_context m_rn2903; + + /** + * Read character data from the device + * + * @param size The maximum number of characters to read + * @return string containing the data read + */ + std::string read(int size); + + /** + * Write character data to the device + * + * @param buffer The string containing the data to write + * @return The number of bytes written + */ + int write(std::string buffer); + + private: + }; +} diff --git a/src/rn2903/rn2903_defs.h b/src/rn2903/rn2903_defs.h new file mode 100644 index 00000000..dfba3047 --- /dev/null +++ b/src/rn2903/rn2903_defs.h @@ -0,0 +1,147 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2017 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. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// maximum buffer size +#define RN2903_MAX_BUFFER (512) + +// size of hex encoded 64b EUI (IPV6 Extended Unique Identifier) +#define RN2903_MAX_HEX_EUI64 (16) + +// default baudrate +#define RN2903_DEFAULT_BAUDRATE (57600) + +// response wait times in milliseconds +#define RN2903_DEFAULT_RESP_DELAY (1000) // 1 second +#define RN2903_DEFAULT_RESP2_DELAY (60000) // 60 seconds + +// This byte sequence must follow all commands. All responses +// will also be followed by these bytes (\r\n - CR LF). +#define RN2903_PHRASE_TERM "\r\n" +#define RN2903_PHRASE_TERM_LEN (2) + +// invalid parameter +#define RN2903_PHRASE_INV_PARAM "invalid_param" +// ok +#define RN2903_PHRASE_OK "ok" + + // RN2903_MAC_STATUS_BITS_T from "mac get status" cmd + typedef enum { + RN2903_MAC_STATUS_JOINED = 0x0001, + + RN2903_MAC_STATUS_MAC_STATUS0 = 0x0002, + RN2903_MAC_STATUS_MAC_STATUS1 = 0x0004, + RN2903_MAC_STATUS_MAC_STATUS2 = 0x0008, + _RN2903_MAC_STATUS_MAC_STATUS_MASK = 7, + _RN2903_MAC_STATUS_MAC_STATUS_SHIFT = 1, + + RN2903_MAC_STATUS_AUTO_REPLY = 0x0010, + RN2903_MAC_STATUS_ADR = 0x0020, + RN2903_MAC_STATUS_SILENT = 0x0040, + RN2903_MAC_STATUS_PAUSED = 0x0080, + RN2903_MAC_STATUS_RFU = 0x0100, + RN2903_MAC_STATUS_LINK_CHK = 0x0200, + + RN2903_MAC_STATUS_CHAN_UPD = 0x0400, + RN2903_MAC_STATUS_OUT_PWR_UPD = 0x0800, + RN2903_MAC_STATUS_NBREP_UPD = 0x1000, + RN2903_MAC_STATUS_PRESCALER_UPD = 0x2000, + RN2903_MAC_STATUS_SECOND_RX_UPD = 0x4000, + RN2903_MAC_STATUS_TX_TIMING_UPD = 0x8000, + } RN2903_MAC_STATUS_BITS_T; + + // RN2903_MAC_STATUS_MAC_STATUS values + typedef enum { + RN2903_MAC_STAT_IDLE = 0, + RN2903_MAC_STAT_TX_IN_PROGESS = 1, + RN2903_MAC_STAT_BEFORE_RX_WIN1 = 2, + RN2903_MAC_STAT_RX_WIN1_OPEN = 3, + RN2903_MAC_STAT_BETWEEN_RX_WIN1_WIN2 = 4, + RN2903_MAC_STAT_RX_WIN2_OPEN = 5, + RN2903_MAC_STAT_ACK_TIMEOUT = 6, + } RN2903_MAC_STATUS_T; + + // Join types + typedef enum { + RN2903_JOIN_TYPE_OTAA = 0, // over-the-air-activation + RN2903_JOIN_TYPE_ABP = 1, // activation-by + // personalization + } RN2903_JOIN_TYPE_T; + + // Join status + typedef enum { + RN2903_JOIN_STATUS_ACCEPTED = 0, + RN2903_JOIN_STATUS_BAD_KEYS = 1, + RN2903_JOIN_STATUS_NO_CHAN = 2, + RN2903_JOIN_STATUS_SILENT = 3, + RN2903_JOIN_STATUS_BUSY = 4, + RN2903_JOIN_STATUS_MAC_PAUSED = 5, + RN2903_JOIN_STATUS_DENIED = 6, + RN2903_JOIN_STATUS_ALREADY_JOINED = 7, + RN2903_JOIN_STATUS_UPM_ERROR = 8, + } RN2903_JOIN_STATUS_T; + + // possible flow control methods + typedef enum { + RN2903_FLOW_CONTROL_NONE = 0, + RN2903_FLOW_CONTROL_HARD, // hardware flow control + } RN2903_FLOW_CONTROL_T; + + // MAC TX message types + typedef enum { + RN2903_MAC_MSG_TYPE_UNCONFIRMED = 0, + RN2903_MAC_MSG_TYPE_CONFIRMED = 1, + } RN2903_MAC_MSG_TYPE_T; + + // MAC TX status + typedef enum { + RN2903_MAC_TX_STATUS_TX_OK = 0, // tx was sent successfully + RN2903_MAC_TX_STATUS_NOT_JOINED = 1, + RN2903_MAC_TX_STATUS_NO_CHAN = 2, + RN2903_MAC_TX_STATUS_SILENT = 3, + RN2903_MAC_TX_STATUS_FC_NEED_REJOIN = 4, // frame counter overflow + RN2903_MAC_TX_STATUS_BUSY = 5, + RN2903_MAC_TX_STATUS_MAC_PAUSED = 6, + RN2903_MAC_TX_STATUS_BAD_DATA_LEN = 7, + RN2903_MAC_TX_STATUS_RX_RECEIVED = 8, // received a packet + RN2903_MAC_TX_STATUS_MAC_ERR = 9, // error during tx + RN2903_MAC_TX_STATUS_UPM_ERROR = 10, // error during tx + } RN2903_MAC_TX_STATUS_T; + + // last command status + typedef enum { + RN2903_RESPONSE_OK = 0, // "ok", or data + RN2903_RESPONSE_INVALID_PARAM = 1, // "invalid_param" + RN2903_RESPONSE_TIMEOUT = 3, + RN2903_RESPONSE_UPM_ERROR = 4, + } RN2903_RESPONSE_T; + +#ifdef __cplusplus +} +#endif