// See config.h on how to configure the sketch #include "config-check.h" #ifdef USE_LOW_POWER_MODE #include #endif #ifdef PERSISTENT_CONFIGURATION_SUPPORT #include #endif #include #include #include #include #include #include "services.h" #include "app.h" #include "Debug.h" #ifdef PIPE_SOIL_MOISTURE_INTERMEDIATE_TEMPERATURE_SET #define FEATURE_TEMPERATURE #endif #ifdef PIPE_DEVICE_INFORMATION_HARDWARE_REVISION_STRING_SET //#define FEATURE_HARDWARE_REVISION #endif #ifdef PIPE_BATTERY_BATTERY_LEVEL_SET #define FEATURE_BATTERY_LEVEL #endif #define xx(s) x(s) #define x(s) #s #ifdef PIPE_DEVICE_INFORMATION_MODEL_NUMBER_STRING_SET #define FEATURE_MODEL_NUMBER const char model_number[] = "Board type id: " xx(SM_BOARD_VERSION); #endif #ifdef PIPE_DEVICE_INFORMATION_FIRMWARE_REVISION_STRING_SET #define FEATURE_FIRMWARE_REVISION #ifdef FIRMWARE_REVISION const char firmware_revision[] = FIRMWARE_REVISION; #else const char firmware_revision[] = "Arduino: " __DATE__; #endif #endif // PIPE_DEVICE_INFORMATION_FIRMWARE_REVISION_STRING_SET #undef x #undef xx #ifdef USE_LOW_POWER static const bool use_low_power=1; #else static const bool use_low_power=0; #endif static void setup_rf(); static void show_pipes(); static services_pipe_type_mapping_t services_pipe_type_mapping[NUMBER_OF_PIPES] = SERVICES_PIPE_TYPE_MAPPING_CONTENT; static const hal_aci_data_t setup_msgs[NB_SETUP_MESSAGES] PROGMEM = SETUP_MESSAGES_CONTENT; // Pipes static const uint8_t sm_pipe_set = PIPE_SOIL_MOISTURE_SOIL_MOISTURE_CONTROL_SET; static const uint8_t sm_pipe_tx = PIPE_SOIL_MOISTURE_SOIL_MOISTURE_CONTROL_TX; static const uint8_t sm_pipe_rx = PIPE_SOIL_MOISTURE_SOIL_MOISTURE_CONTROL_RX_ACK_AUTO; static struct aci_state_t aci_state; static hal_aci_evt_t aci_data; static boolean timing_change_done = false; void __ble_assert(const char *file, uint16_t line) { debug.print("ERROR "); debug.print(file); debug.print(": "); debug.print(line); debug.print("\n"); while (1) {}; } static void go_to_sleep(); extern SoftwareSerial ss; void setup() { #ifdef TXLED0 // An attempt to make sure that the RX and TX LEDs are turned off when running normally. TXLED0; TXLED1; #endif #if defined(BLEND_MICRO_8MHZ) // As the F_CPU = 8000000UL, the USB core make the PLLCSR = 0x02 // But the external xtal is 16000000Hz, so correct it here. PLLCSR |= 0x10; // Need 16 MHz xtal while (!(PLLCSR & (1<evt_opcode) { case ACI_EVT_DEVICE_STARTED: debug.println(F("ACI_EVT_DEVICE_STARTED")); aci_state.data_credit_total = aci_evt->params.device_started.credit_available; debug.print(F("aci_state.data_credit_total=")); debug.println(aci_state.data_credit_total, DEC); switch (aci_evt->params.device_started.device_mode) { case ACI_DEVICE_SETUP: debug.println(F("ACI_DEVICE_SETUP")); rf_started = true; setup_required = true; break; case ACI_DEVICE_STANDBY: debug.println(F("ACI_DEVICE_STANDBY")); if (aci_evt->params.device_started.hw_error) { delay(20); // Magic number used to make sure the HW error event is handled correctly. } else { #ifdef FEATURE_HARDWARE_REVISION lib_aci_device_version(); debug.println(F("Requesting device version.")); #endif #ifdef FEATURE_MODEL_NUMBER debug.print(F("Model number: ")); debug.println(model_number); size_t model_number_size = min(PIPE_DEVICE_INFORMATION_MODEL_NUMBER_STRING_SET_MAX_SIZE, sizeof(model_number)); lib_aci_set_local_data(&aci_state, PIPE_DEVICE_INFORMATION_MODEL_NUMBER_STRING_SET, (uint8_t *) model_number, model_number_size); #endif #ifdef FEATURE_FIRMWARE_REVISION debug.print(F("Firmware revision: ")); debug.println(firmware_revision); size_t firmware_revision_size = min(PIPE_DEVICE_INFORMATION_FIRMWARE_REVISION_STRING_SET_MAX_SIZE, sizeof(firmware_revision)); lib_aci_set_local_data(&aci_state, PIPE_DEVICE_INFORMATION_FIRMWARE_REVISION_STRING_SET, (uint8_t *) firmware_revision, firmware_revision_size); #endif lib_aci_connect(180 /* in seconds */, 0x0050 /* advertising interval 50ms*/); // lib_aci_broadcast(10/* in seconds */, 0x0100 /* advertising interval 100ms */); // ret = lib_aci_open_adv_pipe(PIPE_BATTERY_BATTERY_LEVEL_BROADCAST); // ret = lib_aci_open_adv_pipe(PIPE_SOIL_MOISTURE_SOIL_MOISTURE_LEVEL_BROADCAST); debug.println(F("Advertising started")); } break; case ACI_DEVICE_INVALID: case ACI_DEVICE_TEST: case ACI_DEVICE_SLEEP: // Ignored break; } break; case ACI_EVT_CMD_RSP: // debug.println(F("ACI_EVT_CMD_RSP")); // debug.print(F("aci_evt->params.cmd_rsp.cmd_opcode=")); // debug.println(aci_evt->params.cmd_rsp.cmd_opcode, HEX); // debug.print(F("aci_evt->params.cmd_rsp.cmd_status=")); // debug.println(aci_evt->params.cmd_rsp.cmd_status, HEX); //If an ACI command response event comes with an error -> stop if (aci_evt->params.cmd_rsp.cmd_status != ACI_STATUS_SUCCESS) { //ACI ReadDynamicData and ACI WriteDynamicData will have status codes of //TRANSACTION_CONTINUE and TRANSACTION_COMPLETE //all other ACI commands will have status code of ACI_STATUS_SCUCCESS for a successful command// // debug.print(F("ACI Command ")); // debug.println(aci_evt->params.cmd_rsp.cmd_opcode, HEX); // debug.print(F("Evt Cmd respone: Status ")); // debug.println(aci_evt->params.cmd_rsp.cmd_status, HEX); } #ifdef FEATURE_HARDWARE_REVISION if (aci_evt->params.cmd_rsp.cmd_opcode == ACI_CMD_GET_DEVICE_VERSION) { debug.println(F("Got device version.")); //Store the version and configuration information of the nRF8001 in the Hardware Revision String Characteristic lib_aci_set_local_data(&aci_state, PIPE_DEVICE_INFORMATION_HARDWARE_REVISION_STRING_SET, (uint8_t *)&(aci_evt->params.cmd_rsp.params.get_device_version), sizeof(aci_evt_cmd_rsp_params_get_device_version_t)); } #endif // FEATURE_HARDWARE_REVISION #ifdef FEATURE_TEMPERATURE if (aci_evt->params.cmd_rsp.cmd_opcode == ACI_CMD_GET_TEMPERATURE) { debug.print(F("aci_evt->params.cmd_rsp.params.get_temperature=")); debug.print(aci_evt->params.cmd_rsp.params.get_temperature.temperature_value, DEC); debug.println(); int32_t t = aci_evt->params.cmd_rsp.params.get_temperature.temperature_value; // t is number of 1/4 degrees celcius. // Multiply t by 25 without having to include float support t = t * 16 + t * 8 + t; uint8_t exponent = -2; // example. reading=111. real temperature = 111 / 4 = 27.75 dec C // Calculation: t = 111, formula: t*25 * 10^exp => 2775 * 10^-2 = 27.75 // The value of flags // bit 0: 0=Celcius, 1=Farenheight // bit 1: 0=time stamp field not present, 1=present // bit 2: 0=temperature type field not present, 1=present // bit 3-7: reserved // Reference: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.temperature_measurement.xml struct { uint8_t flags; uint32_t value; // time stamp // temperature type } temperature_measurement = { 0, ((uint32_t)exponent) << 24 | (t & 0x00FFFFFF) }; lib_aci_set_local_data(&aci_state, PIPE_SOIL_MOISTURE_INTERMEDIATE_TEMPERATURE_SET, (uint8_t *) &temperature_measurement, sizeof(temperature_measurement)); } #endif // FEATURE_TEMPERATURE break; case ACI_EVT_CONNECTED: debug.println(F("ACI_EVT_CONNECTED")); timing_change_done = false; aci_state.data_credit_available = aci_state.data_credit_total; debug.print(F("aci_state.data_credit_available=")); debug.println(aci_state.data_credit_available, DEC); #ifdef FEATURE_TEMPERATURE lib_aci_get_temperature(); #endif sm_on_connect(); break; case ACI_EVT_PIPE_STATUS: debug.println(F("ACI_EVT_PIPE_STATUS")); show_pipes(); break; case ACI_EVT_TIMING: debug.println(F("ACI_EVT_TIMING")); break; case ACI_EVT_DISCONNECTED: debug.println(F("ACI_EVT_DISCONNECTED")); lib_aci_connect(180 /* in seconds */, 0x0100 /* advertising interval 100ms*/); debug.println(F("Advertising started")); sm_on_disconnect(); break; case ACI_EVT_DATA_RECEIVED: pipe_number = aci_evt->params.data_received.rx_data.pipe_number; // debug.print(F("ACI_EVT_DATA_RECEIVED: pipe_number=")); // debug.println(pipe_number, DEC); if (pipe_number == sm_pipe_rx) { on_soil_moisture_ctrl(aci_evt->params.data_received.rx_data.aci_data, aci_evt->len); } break; case ACI_EVT_DATA_CREDIT: debug.println(F("ACI_EVT_DATA_CREDIT")); aci_state.data_credit_available = aci_state.data_credit_available + aci_evt->params.data_credit.credit; debug.print(F("aci_state.data_credit_available=")); debug.println(aci_state.data_credit_available, DEC); break; case ACI_EVT_PIPE_ERROR: debug.println(F("ACI_EVT_PIPE_ERROR")); //See the appendix in the nRF8001 Product Specication for details on the error codes debug.print(F("ACI Evt Pipe Error: Pipe #:")); debug.print(aci_evt->params.pipe_error.pipe_number, DEC); debug.print(F(" Pipe Error Code: 0x")); debug.println(aci_evt->params.pipe_error.error_code, HEX); // Increment the credit available as the data packet was not sent. // The pipe error also represents the Attribute protocol Error Response sent from the peer and that should not be counted // for the credit. if (ACI_STATUS_ERROR_PEER_ATT_ERROR != aci_evt->params.pipe_error.error_code) { aci_state.data_credit_available++; } break; case ACI_EVT_HW_ERROR: debug.println(F("ACI_EVT_HW_ERROR")); debug.print(F("HW error: ")); debug.println(aci_evt->params.hw_error.line_num, DEC); for (uint8_t counter = 0; counter <= (aci_evt->len - 3); counter++) { debug.write(aci_evt->params.hw_error.file_name[counter]); //uint8_t file_name[20]; } debug.println(); lib_aci_connect(180 /* in seconds */, 0x0050 /* advertising interval 50ms*/); debug.println(F("Advertising started")); break; case ACI_EVT_INVALID: debug.println(F("ACI_EVT_INVALID")); break; case ACI_EVT_ECHO: debug.println(F("ACI_EVT_ECHO")); break; case ACI_EVT_BOND_STATUS: debug.println(F("ACI_EVT_BOND_STATUS")); break; case ACI_EVT_DATA_ACK: debug.println(F("ACI_EVT_DATA_ACK")); break; case ACI_EVT_DISPLAY_PASSKEY: debug.println(F("ACI_EVT_DISPLAY_PASSKEY")); break; case ACI_EVT_KEY_REQUEST: debug.println(F("ACI_EVT_KEY_REQUEST")); break; } } else { // debug.println(F("No ACI Events available")); // No event in the ACI Event queue and if there is no event in the ACI command queue the arduino can go to sleep // Arduino can go to sleep now // Wakeup from sleep from the RDYN line } /* setup_required is set to true when the device starts up and enters setup mode. * It indicates that do_aci_setup() should be called. The flag should be cleared if * do_aci_setup() returns ACI_STATUS_TRANSACTION_COMPLETE. */ if (setup_required) { int ret = do_aci_setup(&aci_state); debug.print(F("do_aci_setup ret=")); debug.println(ret, DEC); if (SETUP_SUCCESS == ret) { setup_required = false; } } } static void go_to_sleep() { #ifdef USE_LOW_POWER #if defined(__AVR_ATmega32U4__) LowPower.idle(SLEEP_1S, ADC_OFF, TIMER4_OFF, TIMER3_OFF, TIMER1_OFF, TIMER0_OFF, SPI_OFF, USART1_OFF, TWI_OFF, USB_OFF); } #else # warning No sleep support for current CPU architecture. #endif #endif // USE_LOW_POWER } static uint8_t value = 0; void loop() { static unsigned long last = 0, now; aci_loop(); if (debug.available()) { debug.write(debug.read()); } now = millis(); if (now - last > 3000) { last = now; if (!rf_started) { static int count = 0; static bool reset_attempted = false; count++; if (!reset_attempted) { if (count == 3) { reset_attempted = true; debug.println(F("RF did not start, resetting RF")); // asm volatile ("jmp 0"); // lib_aci_pin_reset(); setup_rf(); count = 0; return; } else { debug.println(F("waiting for RF to start")); } } /**/ } else if (!setup_required) { value++; } } sm_loop(); if (use_low_power) { #ifdef SM_DEBUG debug.println(F("Sleeping...")); debug.flush(); #endif // SM_DEBUG go_to_sleep(); } } static void show_pipes() { for (uint8_t i = 1; i <= NUMBER_OF_PIPES; i++) { uint8_t x = lib_aci_is_pipe_available(&aci_state, i); debug.print(F("pipe #")); debug.print(i, DEC); debug.print(F(", available=?")); debug.println(x, DEC); } } #ifdef USE_BATTERY void notify_battery_level(uint8_t value) { static const uint8_t pipe = PIPE_BATTERY_BATTERY_LEVEL_SET; debug.print(F("notify_battery_level, value=")); debug.println(value, DEC); value = value % 101; lib_aci_send_data(pipe, &value, 1); } #endif // USE_BATTERY void notify_soil_moisture(const struct sm_res &res, uint8_t body_len) { uint8_t *data = (uint8_t *)&res; uint8_t len = SM_RES_HEADER_SIZE + body_len; // debug.print(F("notify_soil_moisture, code=")); // debug.print(res.code, DEC); // debug.print(F(", body_len=")); // debug.println(body_len, DEC); // debug.print(F("aci_state.data_credit_available=")); // debug.println(aci_state.data_credit_available, DEC); bool available = lib_aci_is_pipe_available(&aci_state, sm_pipe_tx); // debug.print(F("pipe available=")); // debug.println(available, DEC); // This should probably be an explicit part of the API, but for now it makes it easier to implement a synchronous interface. lib_aci_set_local_data(&aci_state, sm_pipe_set, (uint8_t *)&res, len); // There is no need to lod messages that won't be sent. if (!available) { return; } #ifdef SM_DEBUG debug.println("write_res"); write_res(res); #endif if (aci_state.data_credit_available == 0) { #ifdef SM_DEBUG debug.println("Not enough credits to send notification."); #endif return; } boolean sent = lib_aci_send_data(sm_pipe_tx, data, len); if (sent) { aci_state.data_credit_available--; } else { #ifdef SM_DEBUG debug.println("Sending failed"); #endif } }