// Copyright © 2017 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. #include "TheThingsNetwork.h" #define debugPrintLn(...) \ { \ if (debugStream) \ debugStream->println(__VA_ARGS__); \ } #define debugPrint(...) \ { \ if (debugStream) \ debugStream->print(__VA_ARGS__); \ } #define TTN_HEX_CHAR_TO_NIBBLE(c) ((c >= 'A') ? (c - 'A' + 0x0A) : (c - '0')) #define TTN_HEX_PAIR_TO_BYTE(h, l) ((TTN_HEX_CHAR_TO_NIBBLE(h) << 4) + TTN_HEX_CHAR_TO_NIBBLE(l)) const char ok[] PROGMEM = "ok"; const char on[] PROGMEM = "on"; const char off[] PROGMEM = "off"; const char accepted[] PROGMEM = "accepted"; const char mac_tx_ok[] PROGMEM = "mac_tx_ok"; const char mac_rx[] PROGMEM = "mac_rx"; const char rn2483[] PROGMEM = "RN2483"; const char *const compare_table[] PROGMEM = {ok, on, off, accepted, mac_tx_ok, mac_rx, rn2483}; #define CMP_OK 0 #define CMP_ON 1 #define CMP_OFF 2 #define CMP_ACCEPTED 3 #define CMP_MAC_TX_OK 4 #define CMP_MAC_RX 5 #define CMP_RN2483 6 #define SENDING "Sending: " #define SEND_MSG "\r\n" const char eui[] PROGMEM = "EUI: "; const char battery[] PROGMEM = "Battery: "; const char appEui[] PROGMEM = "AppEUI: "; const char devEui[] PROGMEM = "DevEUI: "; const char band[] PROGMEM = "Band: "; const char data_rate[] PROGMEM = "Data Rate: "; const char rx_delay_1[] PROGMEM = "RX Delay 1: "; const char rx_delay_2[] PROGMEM = "RX Delay 2: "; const char version[] PROGMEM = "Version: "; const char model[] PROGMEM = "Model: "; const char devaddr[] PROGMEM = "DevAddr: "; const char *const show_table[] PROGMEM = {eui, battery, appEui, devEui, band, data_rate, rx_delay_1, rx_delay_2, version, model, devaddr}; #define SHOW_EUI 0 #define SHOW_BATTERY 1 #define SHOW_APPEUI 2 #define SHOW_DEVEUI 3 #define SHOW_BAND 4 #define SHOW_DATA_RATE 5 #define SHOW_RX_DELAY_1 6 #define SHOW_RX_DELAY_2 7 #define SHOW_VERSION 8 #define SHOW_MODEL 9 #define SHOW_DEVADDR 10 const char invalid_sf[] PROGMEM = "Invalid SF"; const char invalid_fp[] PROGMEM = "Invalid frequency plan"; const char unexpected_response[] PROGMEM = "Unexpected response: "; const char send_command_failed[] PROGMEM = "Send command failed"; const char join_failed[] PROGMEM = "Send join command failed"; const char join_not_accepted[] PROGMEM = "Join not accepted: "; const char personalize_not_accepted[] PROGMEM = "Personalize not accepted"; const char response_is_not_ok[] PROGMEM = "Response is not OK: "; const char error_key_length[] PROGMEM = "One or more keys are of invalid length."; const char check_configuration[] PROGMEM = "Check your coverage, keys and backend status."; const char *const error_msg[] PROGMEM = {invalid_sf, invalid_fp, unexpected_response, send_command_failed, join_failed, join_not_accepted, personalize_not_accepted, response_is_not_ok, error_key_length, check_configuration}; #define ERR_INVALID_SF 0 #define ERR_INVALID_FP 1 #define ERR_UNEXPECTED_RESPONSE 2 #define ERR_SEND_COMMAND_FAILED 3 #define ERR_JOIN_FAILED 4 #define ERR_JOIN_NOT_ACCEPTED 5 #define ERR_PERSONALIZE_NOT_ACCEPTED 6 #define ERR_RESPONSE_IS_NOT_OK 7 #define ERR_KEY_LENGTH 8 #define ERR_CHECK_CONFIGURATION 9 const char personalize_accepted[] PROGMEM = "Personalize accepted. Status: "; const char join_accepted[] PROGMEM = "Join accepted. Status: "; const char successful_transmission[] PROGMEM = "Successful transmission"; const char successful_transmission_received[] PROGMEM = "Successful transmission. Received "; const char *const success_msg[] PROGMEM = {personalize_accepted, join_accepted, successful_transmission, successful_transmission_received}; #define SCS_PERSONALIZE_ACCEPTED 0 #define SCS_JOIN_ACCEPTED 1 #define SCS_SUCCESSFUL_TRANSMISSION 2 #define SCS_SUCCESSFUL_TRANSMISSION_RECEIVED 3 const char radio_prefix[] PROGMEM = "radio"; const char radio_set[] PROGMEM = "set"; const char radio_get[] PROGMEM = "get"; const char radio_get_bw[] PROGMEM = "bw"; const char radio_get_prlen[] PROGMEM = "prlen"; const char radio_get_crc[] PROGMEM = "crc"; const char radio_get_cr[] PROGMEM = "cr"; const char radio_get_sf[] PROGMEM = "sf"; const char *const radio_table[] PROGMEM = {radio_prefix, radio_set, radio_get, radio_get_bw, radio_get_prlen, radio_get_crc, radio_get_cr, radio_get_sf}; #define RADIO_PREFIX 0 #define RADIO_SET 1 #define RADIO_GET 2 #define RADIO_GET_BW 3 #define RADIO_GET_PRLEN 4 #define RADIO_GET_CRC 5 #define RADIO_GET_CR 6 #define RADIO_GET_SF 7 const char sys_prefix[] PROGMEM = "sys"; const char sys_sleep[] PROGMEM = "sleep"; const char sys_reset[] PROGMEM = "reset"; const char sys_erase_fw[] PROGMEM = "eraseFW"; const char sys_factory_rst[] PROGMEM = "factoryRESET"; const char sys_set[] PROGMEM = "set"; const char sys_get[] PROGMEM = "get"; const char sys_get_ver[] PROGMEM = "ver"; const char sys_get_vdd[] PROGMEM = "vdd"; const char sys_get_hweui[] PROGMEM = "hweui"; const char sys_set_get_nvm[] PROGMEM = "nvm"; const char sys_set_pindig[] PROGMEM = "pindig"; const char *const sys_table[] PROGMEM = {sys_prefix, sys_sleep, sys_reset, sys_erase_fw, sys_factory_rst, sys_set, sys_get, sys_get_ver, sys_get_vdd, sys_get_hweui, sys_set_get_nvm, sys_set_pindig}; #define SYS_PREFIX 0 #define SYS_SLEEP 1 #define SYS_RESET 2 #define SYS_ERASE_FW 3 #define SYS_FACTORY_RST 4 #define SYS_SET 5 #define SYS_GET 6 #define SYS_GET_VER 7 #define SYS_GET_VDD 8 #define SYS_GET_HWEUI 9 #define SYS_SET_GET_NVM 10 #define SYS_SET_PINDIG 11 const char mac_prefix[] PROGMEM = "mac"; const char mac_reset[] PROGMEM = "reset"; const char mac_tx[] PROGMEM = "tx"; const char mac_join[] PROGMEM = "join"; const char mac_save[] PROGMEM = "save"; const char mac_force_enable[] PROGMEM = "forceENABLE"; const char mac_pause[] PROGMEM = "pause"; const char mac_resume[] PROGMEM = "resume"; const char mac_set[] PROGMEM = "set"; const char mac_get[] PROGMEM = "get"; const char *const mac_table[] PROGMEM = {mac_prefix, mac_reset, mac_tx, mac_join, mac_save, mac_force_enable, mac_pause, mac_resume, mac_set, mac_get}; #define MAC_PREFIX 0 #define MAC_RESET 1 #define MAC_TX 2 #define MAC_JOIN 3 #define MAC_SAVE 4 #define MAC_FORCE_ENABLE 5 #define MAC_PAUSE 6 #define MAC_RESUME 7 #define MAC_SET 8 #define MAC_GET 9 const char mac_devaddr[] PROGMEM = "devaddr"; const char mac_deveui[] PROGMEM = "deveui"; const char mac_appeui[] PROGMEM = "appeui"; const char mac_nwkskey[] PROGMEM = "nwkskey"; const char mac_appskey[] PROGMEM = "appskey"; const char mac_appkey[] PROGMEM = "appkey"; const char mac_pwridx[] PROGMEM = "pwridx"; const char mac_dr[] PROGMEM = "dr"; const char mac_adr[] PROGMEM = "adr"; const char mac_bat[] PROGMEM = "bat"; const char mac_retx[] PROGMEM = "retx"; const char mac_linkchk[] PROGMEM = "linkchk"; const char mac_rxdelay1[] PROGMEM = "rxdelay1"; const char mac_rxdelay2[] PROGMEM = "rxdelay2"; const char mac_band[] PROGMEM = "band"; const char mac_ar[] PROGMEM = "ar"; const char mac_rx2[] PROGMEM = "rx2"; const char mac_ch[] PROGMEM = "ch"; const char mac_gwnb[] PROGMEM = "gwnb"; const char mac_mrgn[] PROGMEM = "mrgn"; const char *const mac_options[] PROGMEM = {mac_devaddr, mac_deveui, mac_appeui, mac_nwkskey, mac_appskey, mac_appkey, mac_pwridx, mac_dr, mac_adr, mac_bat, mac_retx, mac_linkchk, mac_rxdelay1, mac_rxdelay2, mac_band, mac_ar, mac_rx2, mac_ch, mac_gwnb, mac_mrgn}; #define MAC_DEVADDR 0 #define MAC_DEVEUI 1 #define MAC_APPEUI 2 #define MAC_NWKSKEY 3 #define MAC_APPSKEY 4 #define MAC_APPKEY 5 #define MAC_PWRIDX 6 #define MAC_DR 7 #define MAC_ADR 8 #define MAC_BAT 9 #define MAC_RETX 10 #define MAC_LINKCHK 11 #define MAC_RXDELAY1 12 #define MAC_RXDELAY2 13 #define MAC_BAND 14 #define MAC_AR 15 #define MAC_RX2 16 #define MAC_CH 17 #define MAC_GWNB 18 #define MAC_MRGN 19 const char mac_join_mode_otaa[] PROGMEM = "otaa"; const char mac_join_mode_abp[] PROGMEM = "abp"; const char *const mac_join_mode[] PROGMEM = {mac_join_mode_otaa, mac_join_mode_abp}; #define MAC_JOIN_MODE_OTAA 0 #define MAC_JOIN_MODE_ABP 1 const char channel_dcycle[] PROGMEM = "dcycle"; const char channel_drrange[] PROGMEM = "drrange"; const char channel_freq[] PROGMEM = "freq"; const char channel_status[] PROGMEM = "status"; const char *const mac_ch_options[] PROGMEM = {channel_dcycle, channel_drrange, channel_freq, channel_status}; #define MAC_CHANNEL_DCYCLE 0 #define MAC_CHANNEL_DRRANGE 1 #define MAC_CHANNEL_FREQ 2 #define MAC_CHANNEL_STATUS 3 const char mac_tx_type_cnf[] PROGMEM = "cnf"; const char mac_tx_type_ucnf[] PROGMEM = "uncnf"; const char *const mac_tx_table[] PROGMEM = {mac_tx_type_cnf, mac_tx_type_ucnf}; #define MAC_TX_TYPE_CNF 0 #define MAC_TX_TYPE_UCNF 1 #define MAC_TABLE 0 #define MAC_GET_SET_TABLE 1 #define MAC_JOIN_TABLE 2 #define MAC_CH_TABLE 3 #define MAC_TX_TABLE 4 #define SYS_TABLE 5 #define RADIO_TABLE 6 #define ERR_MESSAGE 7 #define SUCCESS_MESSAGE 8 int pgmstrcmp(const char *str1, uint8_t str2Index) { char str2[128]; strcpy_P(str2, (char *)pgm_read_word(&(compare_table[str2Index]))); return memcmp(str1, str2, min(strlen(str1), strlen(str2))); } uint8_t digits(uint8_t port) { if (port >= 100) { return 3; } else if (port >= 10) { return 2; } return 1; } uint8_t receivedPort(const char *s) { uint8_t port = 0; uint8_t i = 0; while (s[i] != ' ' && s[i] != '\0') { port *= 10; port += s[i] - 48; i++; } return port; } TheThingsNetwork::TheThingsNetwork(Stream &modemStream, Stream &debugStream, ttn_fp_t fp, uint8_t sf, uint8_t fsb) { this->debugStream = &debugStream; this->modemStream = &modemStream; this->modemStream->setTimeout(10000); this->fp = fp; this->sf = sf; this->fsb = fsb; } size_t TheThingsNetwork::getAppEui(char *buffer, size_t size) { return readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_APPEUI, buffer, size); } size_t TheThingsNetwork::getHardwareEui(char *buffer, size_t size) { return readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_HWEUI, buffer, size); } void TheThingsNetwork::debugPrintIndex(uint8_t index, const char *value) { char message[100]; strcpy_P(message, (char *)pgm_read_word(&(show_table[index]))); debugPrint(message); if (value) { debugPrintLn(value); } } void TheThingsNetwork::debugPrintMessage(uint8_t type, uint8_t index, const char *value) { char message[100]; switch (type) { case ERR_MESSAGE: strcpy_P(message, (char *)pgm_read_word(&(error_msg[index]))); break; case SUCCESS_MESSAGE: strcpy_P(message, (char *)pgm_read_word(&(success_msg[index]))); break; } debugPrint(message); if (value) { debugPrintLn(value); } else { debugPrintLn(); } } void TheThingsNetwork::clearReadBuffer() { while (modemStream->available()) { modemStream->read(); } } size_t TheThingsNetwork::readLine(char *buffer, size_t size) { size_t read = 0; while (read == 0) { read = modemStream->readBytesUntil('\n', buffer, size); } buffer[read - 1] = '\0'; // set \r to \0 return read; } size_t TheThingsNetwork::readResponse(uint8_t prefixTable, uint8_t index, char *buffer, size_t size) { clearReadBuffer(); sendCommand(prefixTable, 0, true, false); sendCommand(prefixTable, index, false, false); modemStream->write(SEND_MSG); return readLine(buffer, size); } size_t TheThingsNetwork::readResponse(uint8_t prefixTable, uint8_t indexTable, uint8_t index, char *buffer, size_t size) { clearReadBuffer(); sendCommand(prefixTable, 0, true, false); sendCommand(MAC_TABLE, MAC_GET, true, false); sendCommand(indexTable, index, false, false); modemStream->write(SEND_MSG); return readLine(buffer, size); } void TheThingsNetwork::autoBaud() { // Courtesy of @jpmeijers modemStream->setTimeout(2000); uint8_t attempts = 10; size_t length = 0; while (attempts-- && length == 0) { delay(100); modemStream->write((byte)0x00); modemStream->write(0x55); modemStream->write(SEND_MSG); sendCommand(SYS_TABLE, 0, true, false); sendCommand(SYS_TABLE, SYS_GET, true, false); sendCommand(SYS_TABLE, SYS_GET_VER, false, false); modemStream->write(SEND_MSG); length = modemStream->readBytesUntil('\n', buffer, sizeof(buffer)); } delay(100); clearReadBuffer(); modemStream->setTimeout(10000); baudDetermined = true; } void TheThingsNetwork::reset(bool adr) { autoBaud(); size_t length = readResponse(SYS_TABLE, SYS_RESET, buffer, sizeof(buffer)); autoBaud(); length = readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_VER, buffer, sizeof(buffer)); // buffer contains "RN2xx3[xx] x.x.x ...", splitting model from version char *model = strtok(buffer, " "); debugPrintIndex(SHOW_MODEL, model); char *version = strtok(NULL, " "); debugPrintIndex(SHOW_VERSION, version); readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_HWEUI, buffer, sizeof(buffer)); sendMacSet(MAC_DEVEUI, buffer); if (adr) { sendMacSet(MAC_ADR, "on"); } else { sendMacSet(MAC_ADR, "off"); } this->adr = adr; } void TheThingsNetwork::saveState() { debugPrint(SENDING); sendCommand(MAC_TABLE, MAC_PREFIX, true); sendCommand(MAC_TABLE, MAC_SAVE, false); modemStream->write(SEND_MSG); debugPrintLn(); waitForOk(); } void TheThingsNetwork::onMessage(void (*cb)(const uint8_t *payload, size_t size, port_t port)) { messageCallback = cb; } bool TheThingsNetwork::personalize(const char *devAddr, const char *nwkSKey, const char *appSKey) { reset(adr); if (strlen(devAddr) != 8 || strlen(appSKey) != 32 || strlen(nwkSKey) != 32) { debugPrintMessage(ERR_MESSAGE, ERR_KEY_LENGTH); return false; } sendMacSet(MAC_DEVADDR, devAddr); sendMacSet(MAC_NWKSKEY, nwkSKey); sendMacSet(MAC_APPSKEY, appSKey); return personalize(); } bool TheThingsNetwork::personalize() { configureChannels(fsb); setSF(sf); sendJoinSet(MAC_JOIN_MODE_ABP); readLine(buffer, sizeof(buffer)); if (pgmstrcmp(buffer, CMP_ACCEPTED) != 0) { debugPrintMessage(ERR_MESSAGE, ERR_PERSONALIZE_NOT_ACCEPTED, buffer); debugPrintMessage(ERR_MESSAGE, ERR_CHECK_CONFIGURATION); return false; } readResponse(MAC_TABLE, MAC_CH_TABLE, MAC_CHANNEL_STATUS, buffer, sizeof(buffer)); debugPrintMessage(SUCCESS_MESSAGE, SCS_PERSONALIZE_ACCEPTED, buffer); return true; } bool TheThingsNetwork::provision(const char *appEui, const char *appKey) { reset(adr); if (strlen(appEui) != 16 || strlen(appKey) != 32) { debugPrintMessage(ERR_MESSAGE, ERR_KEY_LENGTH); return false; } readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_HWEUI, buffer, sizeof(buffer)); sendMacSet(MAC_DEVEUI, buffer); sendMacSet(MAC_APPEUI, appEui); sendMacSet(MAC_APPKEY, appKey); saveState(); return true; } bool TheThingsNetwork::join(int8_t retries, uint32_t retryDelay) { int8_t attempts = 0; configureChannels(fsb); setSF(sf); while (retries == -1 || attempts <= retries) { attempts++; if (!sendJoinSet(MAC_JOIN_MODE_OTAA)) { debugPrintMessage(ERR_MESSAGE, ERR_JOIN_FAILED); delay(retryDelay); continue; } readLine(buffer, sizeof(buffer)); if (pgmstrcmp(buffer, CMP_ACCEPTED) != 0) { debugPrintMessage(ERR_MESSAGE, ERR_JOIN_NOT_ACCEPTED, buffer); debugPrintMessage(ERR_MESSAGE, ERR_CHECK_CONFIGURATION); delay(retryDelay); continue; } readResponse(MAC_TABLE, MAC_CH_TABLE, MAC_CHANNEL_STATUS, buffer, sizeof(buffer)); debugPrintMessage(SUCCESS_MESSAGE, SCS_JOIN_ACCEPTED, buffer); readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_DEVADDR, buffer, sizeof(buffer)); debugPrintIndex(SHOW_DEVADDR, buffer); return true; } return false; } bool TheThingsNetwork::join(const char *appEui, const char *appKey, int8_t retries, uint32_t retryDelay) { return provision(appEui, appKey) && join(retries, retryDelay); } ttn_response_t TheThingsNetwork::sendBytes(const uint8_t *payload, size_t length, port_t port, bool confirm, uint8_t sf) { if (sf != 0) { setSF(sf); } uint8_t mode = confirm ? MAC_TX_TYPE_CNF : MAC_TX_TYPE_UCNF; if (!sendPayload(mode, port, (uint8_t *)payload, length)) { debugPrintMessage(ERR_MESSAGE, ERR_SEND_COMMAND_FAILED); return TTN_ERROR_SEND_COMMAND_FAILED; } readLine(buffer, sizeof(buffer)); if (pgmstrcmp(buffer, CMP_MAC_TX_OK) == 0) { debugPrintMessage(SUCCESS_MESSAGE, SCS_SUCCESSFUL_TRANSMISSION); return TTN_SUCCESSFUL_TRANSMISSION; } if (pgmstrcmp(buffer, CMP_MAC_RX) == 0) { port_t downlinkPort = receivedPort(buffer + 7); char *data = buffer + 7 + digits(downlinkPort) + 1; size_t downlinkLength = strlen(data) / 2; if (downlinkLength > 0) { uint8_t downlink[downlinkLength]; for (size_t i = 0, d = 0; i < downlinkLength; i++, d += 2) { downlink[i] = TTN_HEX_PAIR_TO_BYTE(data[d], data[d + 1]); } debugPrintMessage(SUCCESS_MESSAGE, SCS_SUCCESSFUL_TRANSMISSION_RECEIVED, data); if (messageCallback) { messageCallback(downlink, downlinkLength, downlinkPort); } } else { debugPrintMessage(SUCCESS_MESSAGE, SCS_SUCCESSFUL_TRANSMISSION); } return TTN_SUCCESSFUL_RECEIVE; } debugPrintMessage(ERR_MESSAGE, ERR_UNEXPECTED_RESPONSE, buffer); return TTN_ERROR_UNEXPECTED_RESPONSE; } ttn_response_t TheThingsNetwork::poll(port_t port, bool confirm) { uint8_t payload[] = {0x00}; return sendBytes(payload, 1, port, confirm); } void TheThingsNetwork::showStatus() { readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_HWEUI, buffer, sizeof(buffer)); debugPrintIndex(SHOW_EUI, buffer); readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_VDD, buffer, sizeof(buffer)); debugPrintIndex(SHOW_BATTERY, buffer); readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_APPEUI, buffer, sizeof(buffer)); debugPrintIndex(SHOW_APPEUI, buffer); readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_DEVEUI, buffer, sizeof(buffer)); debugPrintIndex(SHOW_DEVEUI, buffer); readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_DR, buffer, sizeof(buffer)); debugPrintIndex(SHOW_DATA_RATE, buffer); readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_RXDELAY1, buffer, sizeof(buffer)); debugPrintIndex(SHOW_RX_DELAY_1, buffer); readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_RXDELAY2, buffer, sizeof(buffer)); debugPrintIndex(SHOW_RX_DELAY_2, buffer); } void TheThingsNetwork::configureEU868() { sendMacSet(MAC_RX2, "3 869525000"); sendChSet(MAC_CHANNEL_DRRANGE, 1, "0 6"); char buf[10]; uint32_t freq = 867100000; uint8_t ch; for (ch = 0; ch < 8; ch++) { sendChSet(MAC_CHANNEL_DCYCLE, ch, "799"); if (ch > 2) { sprintf(buf, "%lu", freq); sendChSet(MAC_CHANNEL_FREQ, ch, buf); sendChSet(MAC_CHANNEL_DRRANGE, ch, "0 5"); sendChSet(MAC_CHANNEL_STATUS, ch, "on"); freq = freq + 200000; } } sendMacSet(MAC_PWRIDX, TTN_PWRIDX_EU868); } void TheThingsNetwork::configureUS915(uint8_t fsb) { uint8_t ch; uint8_t chLow = fsb > 0 ? (fsb - 1) * 8 : 0; uint8_t chHigh = fsb > 0 ? chLow + 7 : 71; uint8_t ch500 = fsb + 63; for (ch = 0; ch < 72; ch++) { if (ch == ch500 || (ch <= chHigh && ch >= chLow)) { sendChSet(MAC_CHANNEL_STATUS, ch, "on"); if (ch < 63) { sendChSet(MAC_CHANNEL_DRRANGE, ch, "0 3"); } } else { sendChSet(MAC_CHANNEL_STATUS, ch, "off"); } } sendMacSet(MAC_PWRIDX, TTN_PWRIDX_US915); } void TheThingsNetwork::configureAU915(uint8_t fsb) { uint8_t ch; uint8_t chLow = fsb > 0 ? (fsb - 1) * 8 : 0; uint8_t chHigh = fsb > 0 ? chLow + 7 : 71; uint8_t ch500 = fsb + 63; for (ch = 0; ch < 72; ch++) { if (ch == ch500 || (ch <= chHigh && ch >= chLow)) { sendChSet(MAC_CHANNEL_STATUS, ch, "on"); if (ch < 63) { sendChSet(MAC_CHANNEL_DRRANGE, ch, "0 3"); } } else { sendChSet(MAC_CHANNEL_STATUS, ch, "off"); } } sendMacSet(MAC_PWRIDX, TTN_PWRIDX_AU915); } void TheThingsNetwork::configureAS920_923() { /* RN2903AS 1.0.3rc9 defaults * CH0 = 923.2MHz * CH1 = 923.4MHz */ sendMacSet(MAC_ADR, "off"); // TODO: remove when ADR is implemented for this plan sendMacSet(MAC_RX2, "2 923200000"); char buf[10]; uint32_t freq = 922000000; uint8_t ch; for (ch = 0; ch < 8; ch++) { sendChSet(MAC_CHANNEL_DCYCLE, ch, "799"); if (ch > 1) { sprintf(buf, "%lu", freq); sendChSet(MAC_CHANNEL_FREQ, ch, buf); sendChSet(MAC_CHANNEL_DRRANGE, ch, "0 5"); sendChSet(MAC_CHANNEL_STATUS, ch, "on"); freq = freq + 200000; } } // TODO: SF7BW250/DR6 channel, not properly supported by RN2903AS yet //sendChSet(MAC_CHANNEL_DCYCLE, 8, "799"); //sendChSet(MAC_CHANNEL_FREQ, 8, "922100000"); //sendChSet(MAC_CHANNEL_DRRANGE, 8, "6 6"); //sendChSet(MAC_CHANNEL_STATUS, 8, "on"); // TODO: Add FSK channel on 921800000 sendMacSet(MAC_PWRIDX, TTN_PWRIDX_AS920_923); } void TheThingsNetwork::configureAS923_925() { /* RN2903AS 1.0.3rc9 defaults * CH0 = 923.2MHz * CH1 = 923.4MHz */ sendMacSet(MAC_ADR, "off"); // TODO: remove when ADR is implemented for this plan sendMacSet(MAC_RX2, "2 923200000"); char buf[10]; uint32_t freq = 923600000; uint8_t ch; for (ch = 0; ch < 8; ch++) { sendChSet(MAC_CHANNEL_DCYCLE, ch, "799"); if (ch > 1) { sprintf(buf, "%lu", freq); sendChSet(MAC_CHANNEL_FREQ, ch, buf); sendChSet(MAC_CHANNEL_DRRANGE, ch, "0 5"); sendChSet(MAC_CHANNEL_STATUS, ch, "on"); freq = freq + 200000; } } // TODO: SF7BW250/DR6 channel, not properly supported by RN2903AS yet //sendChSet(MAC_CHANNEL_DCYCLE, 8, "799"); //sendChSet(MAC_CHANNEL_FREQ, 8, "924500000"); //sendChSet(MAC_CHANNEL_DRRANGE, 8, "6 6"); //sendChSet(MAC_CHANNEL_STATUS, 8, "on"); // TODO: Add FSK channel on 924800000 sendMacSet(MAC_PWRIDX, TTN_PWRIDX_AS923_925); } void TheThingsNetwork::configureKR920_923() { sendMacSet(MAC_ADR, "off"); // TODO: remove when ADR is implemented for this plan sendMacSet(MAC_RX2, "0 921900000"); // KR still uses SF12 for now. Might change to SF9 later. //disable two default LoRaWAN channels sendChSet(MAC_CHANNEL_STATUS, 0, "off"); sendChSet(MAC_CHANNEL_STATUS, 1, "off"); char buf[10]; uint32_t freq = 922100000; uint8_t ch; for (ch = 2; ch < 9; ch++) { sendChSet(MAC_CHANNEL_DCYCLE, ch, "799"); sprintf(buf, "%lu", freq); sendChSet(MAC_CHANNEL_FREQ, ch, buf); sendChSet(MAC_CHANNEL_DRRANGE, ch, "0 5"); sendChSet(MAC_CHANNEL_STATUS, ch, "on"); freq = freq + 200000; } sendMacSet(MAC_PWRIDX, TTN_PWRIDX_KR920_923); } void TheThingsNetwork::configureChannels(uint8_t fsb) { switch (fp) { case TTN_FP_EU868: configureEU868(); break; case TTN_FP_US915: configureUS915(fsb); case TTN_FP_AU915: configureAU915(fsb); break; case TTN_FP_AS920_923: configureAS920_923(); break; case TTN_FP_AS923_925: configureAS923_925(); break; case TTN_FP_KR920_923: configureKR920_923(); break; default: debugPrintMessage(ERR_MESSAGE, ERR_INVALID_FP); break; } sendMacSet(MAC_RETX, TTN_RETX); } bool TheThingsNetwork::setSF(uint8_t sf) { uint8_t dr; switch (fp) { case TTN_FP_EU868: case TTN_FP_AS920_923: case TTN_FP_AS923_925: case TTN_FP_KR920_923: dr = 12 - sf; break; case TTN_FP_US915: dr = 10 - sf; case TTN_FP_AU915: dr = 10 - sf; break; } char s[2]; s[0] = '0' + dr; s[1] = '\0'; return sendMacSet(MAC_DR, s); } void TheThingsNetwork::sendCommand(uint8_t table, uint8_t index, bool appendSpace, bool print) { char command[100]; switch (table) { case MAC_TABLE: strcpy_P(command, (char *)pgm_read_word(&(mac_table[index]))); break; case MAC_GET_SET_TABLE: strcpy_P(command, (char *)pgm_read_word(&(mac_options[index]))); break; case MAC_JOIN_TABLE: strcpy_P(command, (char *)pgm_read_word(&(mac_join_mode[index]))); break; case MAC_CH_TABLE: strcpy_P(command, (char *)pgm_read_word(&(mac_ch_options[index]))); break; case MAC_TX_TABLE: strcpy_P(command, (char *)pgm_read_word(&(mac_tx_table[index]))); break; case SYS_TABLE: strcpy_P(command, (char *)pgm_read_word(&(sys_table[index]))); break; case RADIO_TABLE: strcpy_P(command, (char *)pgm_read_word(&(radio_table[index]))); break; default: return; } modemStream->write(command); if (appendSpace) { modemStream->write(" "); } if (print) { debugPrint(command); debugPrint(F(" ")); } } bool TheThingsNetwork::sendMacSet(uint8_t index, const char *value) { clearReadBuffer(); debugPrint(SENDING); sendCommand(MAC_TABLE, MAC_PREFIX, true); sendCommand(MAC_TABLE, MAC_SET, true); sendCommand(MAC_GET_SET_TABLE, index, true); modemStream->write(value); modemStream->write(SEND_MSG); debugPrintLn(value); return waitForOk(); } bool TheThingsNetwork::waitForOk() { readLine(buffer, sizeof(buffer)); if (pgmstrcmp(buffer, CMP_OK) != 0) { debugPrintMessage(ERR_MESSAGE, ERR_RESPONSE_IS_NOT_OK, buffer); return false; } return true; } bool TheThingsNetwork::sendChSet(uint8_t index, uint8_t channel, const char *value) { clearReadBuffer(); char ch[5]; if (channel > 9) { ch[0] = ((channel - (channel % 10)) / 10) + 48; ch[1] = (channel % 10) + 48; ch[2] = '\0'; } else { ch[0] = channel + 48; ch[1] = '\0'; } debugPrint(F(SENDING)); sendCommand(MAC_TABLE, MAC_PREFIX, true); sendCommand(MAC_TABLE, MAC_SET, true); sendCommand(MAC_GET_SET_TABLE, MAC_CH, true); sendCommand(MAC_CH_TABLE, index, true); modemStream->write(ch); modemStream->write(" "); modemStream->write(value); modemStream->write(SEND_MSG); debugPrint(channel); debugPrint(F(" ")); debugPrintLn(value); return waitForOk(); } bool TheThingsNetwork::sendJoinSet(uint8_t type) { clearReadBuffer(); debugPrint(F(SENDING)); sendCommand(MAC_TABLE, MAC_PREFIX, true); sendCommand(MAC_TABLE, MAC_JOIN, true); sendCommand(MAC_JOIN_TABLE, type, false); modemStream->write(SEND_MSG); debugPrintLn(); return waitForOk(); } bool TheThingsNetwork::sendPayload(uint8_t mode, uint8_t port, uint8_t *payload, size_t length) { clearReadBuffer(); debugPrint(F(SENDING)); sendCommand(MAC_TABLE, MAC_PREFIX, true); sendCommand(MAC_TABLE, MAC_TX, true); sendCommand(MAC_TX_TABLE, mode, true); char sport[4]; if (port > 99) { sport[0] = ((port - (port % 100)) / 100) + 48; sport[1] = (((port % 100) - (port % 10)) / 10) + 48; sport[2] = (port % 10) + 48; sport[3] = '\0'; } else if (port > 9) { sport[0] = ((port - (port % 10)) / 10) + 48; sport[1] = (port % 10) + 48; sport[2] = '\0'; } else { sport[0] = port + 48; sport[1] = '\0'; } modemStream->write(sport); modemStream->print(" "); debugPrint(sport); debugPrint(F(" ")); uint8_t i = 0; for (i = 0; i < length; i++) { if (payload[i] < 16) { modemStream->print("0"); modemStream->print(payload[i], HEX); debugPrint(F("0")); debugPrint(payload[i], HEX); } else { modemStream->print(payload[i], HEX); debugPrint(payload[i], HEX); } } modemStream->write(SEND_MSG); debugPrintLn(); return waitForOk(); } void TheThingsNetwork::sleep(uint32_t mseconds) { if (mseconds < 100) { return; } debugPrint(F(SENDING)); sendCommand(SYS_TABLE, SYS_PREFIX, true); sendCommand(SYS_TABLE, SYS_SLEEP, true); sprintf(buffer, "%lu", mseconds); modemStream->write(buffer); modemStream->write(SEND_MSG); debugPrintLn(buffer); } void TheThingsNetwork::wake() { autoBaud(); } void TheThingsNetwork::linkCheck(uint16_t seconds) { clearReadBuffer(); debugPrint(SENDING); sendCommand(MAC_TABLE, MAC_PREFIX, true); sendCommand(MAC_TABLE, MAC_SET, true); sendCommand(MAC_GET_SET_TABLE, MAC_LINKCHK, true); sprintf(buffer, "%u", seconds); modemStream->write(buffer); modemStream->write(SEND_MSG); debugPrintLn(buffer); waitForOk(); } uint8_t TheThingsNetwork::getLinkCheckGateways() { readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_GWNB, buffer, sizeof(buffer)); return strtol(buffer, NULL, 10); } uint8_t TheThingsNetwork::getLinkCheckMargin() { readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_MRGN, buffer, sizeof(buffer)); return strtol(buffer, NULL, 10); }