When a device is to be deployed on the field and it will be unattended, it is useful the get some status data from it to determine its operating conditions. In this case, a GNSS receiver is put into an enclosure and installed in a mast on top of a building, powered by a 12V battery which is connected using a ~25m cable and installed in a cabinet or office indoors. This GNSS receiver is a 3rd party unit and it doesn't report its status by itself, therefore, we decided to put another device in the box where it is and monitor it externally. This is part of a project for a client and the system will be deployed at their site, so we will not have access to the roof or to the site where the battery is (We can ask for permission, but we prefer to be non-invasive and make trials independently). Then, in order to get some device health information we decided to make a sensing device for measuring voltages and temperatures inside the enclosure and send them to the cloud. This was done using an Arduino MKR1000 IoT Kit (From a previous Roadtest) and Thingsboard as IoT Cloud platform for receiving and visualising the data.
Introduction
In an RTK GNSS system, one receiver works as a base station calculating its position from satellites data, and as its position is fixed, it calculates some corrections and then broadcast them. On the other side, a second GNSS receiver take those corrections in order to improve the accuracy of its calculated position. A more detailed description can be found in this article: "How RTK works". As the receiver we are using is not for outdoor applications, we assembly it in a weatherproof enclosure and the only incoming cable is for power and comms. The assembled unit is the one below and as can be seen, only two antennas (GNSS and RF) and a cable gland are exposed.
Now, the idea is that the base station can be a standalone, unattended unit working 24/7 we need to know at least some information about it, like the battery voltage and the internal temperature. The client of the project will install it on the roof of a building and put the battery indoors, about 25m away from the unit. Measuring the voltage at the battery or opening the box to check the temperature is not the best option as we would need to ask for permission, take someone out of his/her work, etc., so we prefer to do something that let us make trials and work independently. The problem is summarised below.
Based on this, we decided to develop a small IoT-based device that helps to monitor the health status of the base station unit. The idea is to use the Arduino MKR1000 IoT kit which was part of a previous Roadtest, and send the data to an IoT cloud platform. In this case, Thingsboard was chosen because was used for previous projects. For describing this development, this post is divided into three sections: Design, Development and Testing. Each section will show the aspects of both hardware, firmware and software.
Section 1: Design
The overall requirement is to create a system to measure 2 voltages (5v and 12v) and 2 temperatures, the measurement period will be 1 minute and the data should be sent to the cloud for its visualisation and analysis. As the GNSS receiver provides a Pulse-Per-Second Signal, this will be used for timing one minute between measurements. The general block diagram of the solution is presented below:
Hardware
Voltage Measurement
Inside the GNSS receiver enclosure, there are 12V from the battery and 5V regulated which sometimes are used with a power bank for trials out-of-the-roof. As the Arduino MKR1000 inputs only accept 3.3 volts signals, two voltage dividers were designed for this purpose. The first one for the 5V (Vcc) uses 2 10k resistors, which provides a factor of 0.5, and with a maximum value of 2.5V when the input is 5V. This output will be connected to the Analog input 0 (A0). For measuring the battery voltage (Vbat) the divider uses one 1K and one 220 resistors, for a factor of 0.18 which outputs 2.7 when the input voltage is 15V. This output will be connected the analogue input 1 (A1).
Temperature Measurement
For measuring temperature I had in stock one NTC thermistor (B57863s0103f040) and one temperature sensor from the Arduino IoT kit: a TMP36. For the first one, a voltage divider with a 10K resistor is used, powered by 3.3 volts from the Arduino board, the output of this divider is used with the analogue channel 2 (A2). The TMP36 sensor is powered from 3.3 volts as well and the output is connected directly to the analogue input 3 (A3).
Digital IOs
The PPS signal from the GNSS receiver is connected to pin 4 of the Arduino and an LED for visualisation purposes is connected to pin 7.
The schematics of the hardware design is shown in the following picture.
Firmware
The firmware is composed of four parts: Interrupt handling for receiving and counting the pulses from the PPS signal and triggering the measurement, the RTC module for updating it and counting one-minute intervals as back up, the wifi control module for scanning, connecting and retrying connections, and finally, the measurement and sending module which reads the voltage values from the analogue channels, converts them to variables and then format the data for sending it to the cloud. A sketch of the modules is shown below:
Software
As Thingsboard is a developed platform, only a draft of the desired dashboard is done at this stage. It is defined that the dashboard contains the last values of the variables Vbat, Vcc, Temperature 1 and Temperature 2. Also, it is desired to include short-term and long term graphs of the evolution of the variables. Something like this:
Section 2: Development
Hardware
The initial implementation of the hardware was done on a breadboard using through-hole resistors, one NTC thermistor, one TMP36 temperature sensor, one LED and jumper wires. It was tested both with a 5v power bank and inside the enclosure measuring both 12v and 5v. The prototype is shown in the following pictures:
Firmware
As I was relatively new to development with Arduino, I divided the learning/development process into steps, related to the modules defined in the previous section. First, I started with interrupt handling. Then I included the measurement part. After that, I included the WiFi section and the RTC part. Finally, I wrote the code for formatting the data and generating the post request to Thingsboard. For doing so, three libraries were used: WiFi101, RTCZero and ArduinoHttpClient.
The code used is shown below:
#include <WiFi101.h> #include <RTCZero.h> #include <ArduinoHttpClient.h> char ssid[] = "SECRET"; // your network SSID (name) char key[] = "SECRET"; // your network key #define ACCESS_TOKEN "/api/v1/SECRET/telemetry" #define CONTENT_TYPE "application/json" char tb_server[] = "demo.thingsboard.io"; int tb_port = 80; char tb_suffix[] = "/api/v1/%s/telemetry"; RTCZero rtc; int rtc_configure = 1; int send_status = 0; volatile uint8_t pulse = 1; volatile uint8_t pulse_cnt = 0; const uint8_t interruptPin = 4; const uint8_t ledPin = 6; const uint8_t ledPinpps = 7; uint8_t state = 0; uint8_t post_state = 0; unsigned long sent_epoch = 0; char vars_msg_buffer[128] = ""; char data_msg_buffer[128] = ""; int wifi_ctrl_fg = 1; int wifi_status = WL_IDLE_STATUS; WiFiClient wifi; HttpClient client = HttpClient(wifi, tb_server, tb_port); #define V1_PIN A0 #define V2_PIN A1 #define T1_PIN A2 #define T2_PIN A3 #define D1_R1 10000.0 #define D1_R2 10000.0 #define D2_R1 1000.0 #define D2_R2 220.0 #define D1_ratio ((float)D1_R2/(D1_R1+D1_R2)) #define D2_ratio ((float)D2_R2/(D2_R1+D2_R2)) #define VREF1 3.3 #define VREF2 3.3 #define ADC_MAX_VAL 4096 #define ADC_VREF 3.3 #define VOLTAGE 0 #define TEMPERATURE 1 #define TEMPERATURE_TMP 2 uint16_t ch1, ch2, ch3, ch4; float v1, v2, v3, v4; float var1, var2, var3, var4; void setup() { // put your setup code here, to run once: pinMode(ledPin, OUTPUT); pinMode(ledPinpps, OUTPUT); pinMode(interruptPin, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(interruptPin), pulse_int, RISING); Serial.begin(9600); analogReadResolution(12); rtc.begin(); } void loop() { // put your main code here, to run repeatedly: if ((wifi_status == WL_CONNECTED) && (pulse == 1)){ Serial.print("rtc_configure: ");Serial.println(rtc_configure); if (rtc_configure == -1){ rtc_configure = 1; } post_state ^= 1; Serial.println("flag activated"); Serial.println(state); Serial.println(pulse_cnt); digitalWrite(ledPin, post_state); read_analog_pins(); get_voltages(); get_variables(); create_vars_message(vars_msg_buffer); send_status = tb_send_data(vars_msg_buffer); if (send_status == -2){ wifi_ctrl_fg = 1; } post_state = 0; digitalWrite(ledPin, post_state); pulse = 0; sent_epoch = rtc.getEpoch(); } if (wifi_ctrl_fg != 0){ wifi_ctrl_fg = wifi_ctrl(); wifi_status = WiFi.status(); Serial.print("wifi status: ");Serial.println(wifi_status); } if((wifi_status == WL_CONNECTED) && (rtc_configure == 1)){ Serial.println("Wifi connected, Updating RTC"); rtc_configure = 0; update_rtc(); if (rtc_configure == 0){ pulse = 1; sent_epoch = 0; } } if (sent_epoch != 0){ if (rtc.getEpoch() - sent_epoch > 60){ pulse = 1; } } } void pulse_int() { pulse_cnt++; state ^= 1; digitalWrite(ledPinpps, state); if (pulse_cnt >= 60){ pulse = 1; pulse_cnt=0; } } void read_analog_pins(){ ch1 = analogRead(V1_PIN); ch2 = analogRead(V2_PIN); ch3 = analogRead(T1_PIN); ch4 = analogRead(T2_PIN); } float reading2volts(uint16_t value, float vref){ return (((float)value/ADC_MAX_VAL)*vref); } void get_voltages(){ v1 = reading2volts(ch1, ADC_VREF); v2 = reading2volts(ch2, ADC_VREF); v3 = reading2volts(ch3, ADC_VREF); v4 = reading2volts(ch4, ADC_VREF); } float volts2variable(float volts, uint8_t type, float param1){ float result = -100; float r = 0; Serial.print("volts: ");Serial.println(volts); if (type == VOLTAGE){ result = volts/param1; } else if (type == TEMPERATURE){ r = (volts*10000.0)/(3.3-volts); result = res2temp(r/10000.0); } else if (type == TEMPERATURE_TMP){ result = (volts-0.5)*100.0; } return result; } float res2temp(float ratio){ float temp_vals[7] = {-55, 15, 20, 25, 30, 35, 155}; float temp_ratios[7] = {96.3, 1.571, 1.249, 1, 0.8057, 0.6531, 0.01653}; float temp = -999,m, dy, dx; int i=0; Serial.print("ratio: ");Serial.println(ratio); for (i=0; i<6; i++){ if ((ratio <= temp_ratios[i]) && (ratio > temp_ratios[i+1])){ Serial.print("Using index: ");Serial.println(i); dy = (temp_vals[i]-temp_vals[i+1]); dx = (temp_ratios[i]-temp_ratios[i+1]); m = dy/dx; Serial.print("dy: ");Serial.println(dy); Serial.print("dx: ");Serial.println(dx); Serial.print("m: ");Serial.println(m); temp = m*(ratio-temp_ratios[i+1]) + temp_vals[i+1]; break; } } return temp; } void get_variables(){ var1 = volts2variable(v1, VOLTAGE, D1_ratio); var2 = volts2variable(v2, VOLTAGE, D2_ratio); var3 = volts2variable(v3, TEMPERATURE,0); var4 = volts2variable(v4, TEMPERATURE_TMP,0); } void create_vars_message(char* buffer){ const char * var_names[4] = {"vcc","v_bat","temp_1","temp_2"}; float var_vals[4] = {var1, var2, var3, var4}; char* var_vals_str[4] = {}; int ret = 0; ret = sprintf(buffer +ret, "{"); for (int i=0; i < 4; i++){ ret += sprintf(buffer +ret, "\"%s\": %f, ",var_names[i], var_vals[i]); } ret = sprintf(buffer + ret - 2, "}"); Serial.print("Current buffer: "); Serial.println(buffer); } void create_data_message(char* buffer){ if (rtc_configure == 0){ ; }else { ; } } //---- WiFi functions --- void printWiFiData() { // print your WiFi shield's IP address: IPAddress ip = WiFi.localIP(); Serial.print("IP Address: "); Serial.println(ip); Serial.println(ip); // print your MAC address: byte mac[6]; WiFi.macAddress(mac); Serial.print("MAC address: "); printMacAddress(mac); } void printCurrentNet() { // print the SSID of the network you're attached to: Serial.print("SSID: "); Serial.println(WiFi.SSID()); // print the MAC address of the router you're attached to: byte bssid[6]; WiFi.BSSID(bssid); Serial.print("BSSID: "); printMacAddress(bssid); // print the received signal strength: long rssi = WiFi.RSSI(); Serial.print("signal strength (RSSI):"); Serial.println(rssi); // print the encryption type: byte encryption = WiFi.encryptionType(); Serial.print("Encryption Type:"); Serial.println(encryption, HEX); Serial.println(); } void printMacAddress(byte mac[]) { for (int i = 5; i >= 0; i--) { if (mac[i] < 16) { Serial.print("0"); } Serial.print(mac[i], HEX); if (i > 0) { Serial.print(":"); } } Serial.println(); } int wifi_ctrl(){ #define WIFI_CTRL_STATE_INIT 0 #define WIFI_CTRL_STATE_CONNECTING 1 #define WIFI_CTRL_STATE_CONNECTED 2 #define WIFI_CTRL_STATE_DISCONNECTED 3 #define WIFI_CTRL_STATE_SCANNING 4 static uint8_t wifi_ctrl_state = WIFI_CTRL_STATE_INIT; int ret_value = 0; switch (wifi_ctrl_state){ case WIFI_CTRL_STATE_INIT: Serial.print("Attempting to connect to WPA SSID: "); Serial.println(ssid); // Connect to WPA/WPA2 network: WiFi.begin(ssid, key); // wait 10 seconds for connection: // delay(10000); wifi_ctrl_state = WIFI_CTRL_STATE_CONNECTING; ret_value = 1; break; case WIFI_CTRL_STATE_CONNECTING: ret_value = 2; if (WiFi.status() == WL_CONNECTED){ wifi_ctrl_state = WIFI_CTRL_STATE_CONNECTED; } break; case WIFI_CTRL_STATE_CONNECTED: // you're connected now, so print out the data: Serial.print("You're connected to the network"); printCurrentNet(); printWiFiData(); ret_value = 0; break; } return ret_value; } void update_rtc(){ unsigned long epoch; int numberOfTries = 0, maxTries = 6; do { epoch = WiFi.getTime(); numberOfTries++; } while ((epoch == 0) && (numberOfTries < maxTries)); if (numberOfTries == maxTries) { Serial.print("NTP unreachable!!"); rtc_configure = -1; } else { Serial.print("Epoch received: "); Serial.println(epoch); rtc.setEpoch(epoch); Serial.println(); } } int tb_send_data(char * message){ Serial.println("making POST request"); client.post(ACCESS_TOKEN, CONTENT_TYPE, message); // read the status code and body of the response int statusCode = client.responseStatusCode(); String response = client.responseBody(); Serial.print("Status code: "); Serial.println(statusCode); Serial.print("Response: "); Serial.println(response); Serial.println("Wait five seconds"); delay(5000); return statusCode; }
Software
I already had an account in Thingsboard (Free demo version), so the first step is to create a new device. Then I can copy the access token for that device, which is used in the firmware for making the POST request with the data to be sent. Once the device is ready and receiving data, a new dashboard can be created. Inside the dashboard, several options of widgets can be added and organised in a grid manner. For our case, we chose four gauge widgets for displaying the latest values and 4 graph widgets for short- and long- term plotting of the variables:
Section 3: Testing
Here there is a video of the system working from a battery bank. When the temperature sensors are disturbed the dashboard widgets are updated.
And here is a screenshot of the dashboard receiving data from the health monitor once it is installed inside the enclosure:
Conclusions and Future Work
The developed system accomplishes the established requirements: Measure 2 voltages, 2 temperatures and send these values to the cloud for visualisation and managing. Both the Arduino IoT Kit and Thingsboard platform are good tools for rapid deployments of IoT Solutions, providing a good option for rapid prototyping and validation of proofs-of-concept. Although there was some previous experience with Arduino development and Thingsboard, the total time spent in this project from design to testing was less than a week (~40h), Starting from scratch could be 2 weeks, in my opinion.
However, despite the goal was reached there are different aspects of the project that can be or must be improved, especially in terms of board, power consumption, firmware optimisation and dashboard adjustments. Here is a comprehensive list of these aspects:
- Hardware:
- Custom board development: This will reduce the size and area of the health monitor.
- Power and safety protections: It is a good practice to include some protections for over-voltage or reverse-polarisation both at the source and inputs. Also, some capacitors close to the ICs in the power inputs will help to the stability in case of transients.
- Reduce power consumption
- At the moment, power is not a constrain as we are using a 12V 32Ah battery but in order to improve our system, power consumption must be reduced. This involves changes in hardware and firmware.
- Enable channels before reading: All the measuring channels using voltage dividers should include a way to enable/disable them in order to eliminate their current consumption while they are not being measured. This is usually done using MOSFETs to power the dividers, wait some milliseconds for the signal to stabilise, measure it and then disable the channel.
- Use the microcontroller in low power: For this type of applications, the actual time the microcontrollers is working is in the order of seconds, including the detection of the trigger to measure, enable the channels, measure, format the data and send it to the proper place. The rest of the time it could be sleeping or in a Low-power mode. For certain microcontrollers, the current consumption in this mode could be less than 1mA, and this will be close to the average consumption if the application spent 5 seconds for measuring and sending data and only does it once in a minute. 5/60 is about 8% of the time in active mode and 92% in low power mode.
- Firmware optimisation
- Modular code: By separating the functional modules of the system, it is possible to reuse parts of it for other projects or platforms, or for improvements of the same, like changing the sending mechanism to Bluetooth or LoRaWAN, Adding additional channels for measuring, including a display for local monitoring, changing the trigger for measuring, etc.
- Modification of Properties in runtime: For making the system more flexible, certain parameters and properties should be modified for the user in runtime, not in code. For example Wifi network details, measuring period, enable/disable channels, etc.
-
- OTA firmware upgrade: An important part of devices is their ability to be updated without debug or programming tools. Here is where Over-The-Air upgrades provide an advantage for updating the devices with bug fixes, security updates or new features, and with proper documentation, this can be done by the end-user with little to no technical knowledge.
- Dashboard adjustments:
- Notification System and alarms: Thingsboard allows creating notifications and alarms based on the values of the variables, in this way the person in charge can be alerted vía email or text when the values are outside an expected range. For example, high temperature, low battery voltage or inactivity.
References
- B57863s0103f040 NTC Thermistor datasheet: https://product.tdk.com/info/en/documents/data_sheet/50/db/ntc/NTC_Mini_sensors_S863.pdf
Top Comments