From Digi: "MicroPython Examples for Edge Computing Part 3: Bandwidth Management"
Welcome to the third post in our MicroPython series. You can find the first two at the following links:
- • Hands-on MicroPython Programming Examples for Edge Computing: Part 1
- • Hands-on MicroPython Programming Examples for Edge Computing: Part 2
Bandwidth Management and the ROI of IoT Projects: Introduction
IoT sensors and devices live in the real world, often at far-flung locations. They need to communicate with home base, yet that communication comes with costs. Mobile data plans have come down tremendously in cost, but they still aren’t free. Each byte sent adds to that outlay. Wireless data transmission consumes battery power. The more a device communicates, the larger a battery it requires and the more frequently that battery must be changed. Spending for data, parts and labor will impact our IoT project’s return on investment. Luckily, by reducing the amount of communication in our system, the entire project can become cost-effective.
Edge computing can save money by decreasing the frequency and amount of needless communication, while prioritizing the critical data required to make business decisions. “Edge” refers to the edges of a network — where your IoT devices are located. The edge is distinct from the server or cloud application that typically serves as the brains of the IoT system, but is far away from the devices themselves.
By shifting a bit of that computing brainpower close to the devices, local logic can determine if a given event is worth the cost of communicating about it. For example, a liquid level sensor in a storage tank might take a reading every second, but most of these readings will be exactly the same. Without edge computing, we must to send every reading to a cloud application to determine if it matters, including repeats of the same number. This wastes bandwidth, battery, labor and money. A far better solution would be to have the device determine locally that a number was meaningful before sending it. That’s edge computing. It uses simple, low-cost local computing right on the device to make smart decisions. Now our level sensor can stifle repetitive transmissions and prioritize critical alerts. System costs decline while overall quality improves. And this is only one of edge computing’s many capabilities.
There are several methods for managing bandwidth at the edge. Most systems implement a minimum rate of “heartbeat” transmissions that confirm device health and connectivity. Then filtering algorithms can prioritize the most important data for immediate transmission. Duplicate data that’s of no interest can be suppressed right at the source, saving on transmission, battery and cloud storage. Data can be also be aggregated locally so that useful summaries can replace torrents of unprocessed numbers. The full data set can be retained locally to support special investigations or debugging, with edge intelligence managing the local storage so it doesn’t run out of room.
About the Examples
A truck will travel from Galveston TX to Cincinnati OH with a load of fresh fish, ideally stored at 40ºF. We need to log its internal temperature regularly to prove food safety and contract compliance. If the load gets too hot or too cold, an alert needs to go out immediately, and we want to start logging temperatures more frequently to get higher resolution data of the event. We want to save bandwidth and battery by sending only the data we need, given the current situation.
For our examples, we’ll use a set point of 72º with a safe range of 65º-80º to simplify testing during development. Each of these examples delivers its data as text messages for fast, simple feedback. In production you can easily switch to the Digi Remote Manager® version of the same code, provided for each example. The DRM version will post to an online data stream that can be viewed through the DRM interface or accessed externally using an API. See our previous post for more information on Digi Remote Manager, or visit the Digi Remote Manager site page.
Heartbeat Data
Reducing communications reduces bandwidth use and can save us money. However we need to hear from our devices every once in a while, so that we know they’re still alive, and to log a minimum amount of data. This is typically accomplished by sending regular “heartbeat” or status messages, even when no other events have triggered a transmission. For example a flood sensor would only send alerts when it detects water. But we’d also want to confirm it is alive and working on a daily basis. Even when there’s no water detected, it could send a heartbeat message once per day, confirming that it’s still alive and dry.
To begin, let’s set up this heartbeat message. This will establish the minimum amount of contact your IoT device will make. We will build on this code throughout the following examples. Each addition demonstrates strategies for reducing unnecessary bandwidth use without impacting the overall application.
Set Up the Hardware
If you missed the first post in this series, please visit Hands-on MicroPython Programming Examples for Edge Computing: Part 1 and work through the following items to prepare for this second project:
- Getting Started: Demonstrates how to set up the hardware and software you’ll need.
- Hello World Example: Teaches how to upload code to Digi XBee3.
- Sense, Transform and Send a Value: Shows how to take a temperature reading and send it as a text message.
Once you have set up the Digi XBee3 hardware, hooked up the TMP36 temperature sensor, connected it to the configuration software, and opened the MicroPython terminal in XCTU, you are ready to begin. Your setup should look similar to this one:
Load the Code
This heartbeat example code is similar to our original temperature sensing code in the first post in this series. There are two functional changes; it runs continuously now, and that the wait_time is extended so that messages only arrive every 15 minutes. Structural changes were also made, organizing the code into functions to make it more modular and readable as we build it out further. These don’t change the behavior, they just make it easier for us humans to understand what’s happening.
Copy the below code to a text editor like Notepad. Be sure to enter your own phone number, replacing “your_mobile_number_here” before uploading the code. Enter it just as you would dial it on a cell phone, including the + symbol if needed. By default, this program sends a temperature reading every hour. You can customize that by changing the wait_time variable as desired.
Remember, this sample code must be edited before you upload it.
SMS Heartbeat Example
# Digi XBee3 Cellular Heartbeat Example # uses a TMP36 to measure temperature and send it as an SMS message # by default repeating once every 15 minutes, continuously # ENTER YOUR PHONE NUMBER, REPLACING "your_mobile_number_here" BEFORE UPLOADING THIS CODE! import network from machine import ADC from time import sleep heartbeat_wait = 900 # seconds between default measurements number = "your_mobile_number_here" # this phone number receives the text notifications def get_temp(): temp_pin = ADC("D0") temp_raw = temp_pin.read() print("Raw pin reading: %d" % temp_raw) # convert temperature to proper units temperatureC = (int((temp_raw * (2500/4096)) - 500) / 10) print("Temperature: %d Celsius" % temperatureC) temperatureF = (temperatureC * 9.0 / 5.0) + 32.0; print("Temperature: %d Fahrenheit" % temperatureF) return temperatureF def send_SMS(phone_number, payload): c = network.Cellular() while not c.isconnected(): print("waiting for cell network...") sleep(1.5) # Pause 1.5 seconds between checking connection print("connected to cell network.") try: c.sms_send(phone_number, payload) print("message sent successfully to " + phone_number) except Exception as e: print("Send failure: " + str(e)) while True: tempF = get_temp() # read temperature value & print to debug message = ("Temperature: %d'F" % tempF) # Message to send out send_SMS(number, message) # send as text to phone number sleep(heartbeat_wait) # wait between cycles |
Once the code has been edited by adding your mobile phone number, it can be uploaded in XCTU at the MicroPython Terminal in the usual way:
- Press Ctrl+F at the >>> prompt to put MicroPython into Flash Compile mode.
- Copy the script from your text editor and paste it into the MicroPython Terminal.
- Press Ctrl+D to finish and answer N when you’re asked.
- Finally press Ctrl+R to run your code. You can stop execution by pressing Ctrl-C.
Use It
With the heartbeat example running, you should begin receiving text messages. If you left the settings at their default, you’ll get one message every 15 minutes. The results on your phone should look like this:
To run the same example, posting to Digi Remote Manager, use the following code.
Digi Remote Manager Heartbeat Example
# Digi XBee3 Cellular Heartbeat Example # uses a TMP36 to measure temperature and send it to Digi Remote Manager # by default repeating once every 15 minutes, continuously # ENTER YOUR DRM username and password, REPLACING "your_username_here" etc. BEFORE UPLOADING THIS CODE! from machine import ADC from time import sleep from remotemanager import RemoteManagerConnection from xbee import atcmd heartbeat_wait = 900 # seconds between default measurements username = 'your_username_here' #enter your username! password = 'your_password_here' #enter your password! # Device Cloud connection info stream_id = 'temperature' stream_type = 'FLOAT' stream_units = 'degrees F' description = "temperature example" # prepare for connection credentials = {'username': username, 'password': password} stream_info = {"description": description, "id": stream_id, "type": stream_type, "units": stream_units} ai_desc = { 0x00: 'CONNECTED', 0x22: 'REGISTERING_TO_NETWORK', 0x23: 'CONNECTING_TO_INTERNET', 0x24: 'RECOVERY_NEEDED', 0x25: 'NETWORK_REG_FAILURE', 0x2A: 'AIRPLANE_MODE', 0x2B: 'USB_DIRECT', 0x2C: 'PSM_DORMANT', 0x2F: 'BYPASS_MODE_ACTIVE', 0xFF: 'MODEM_INITIALIZING', } def watch_ai(): old_ai = -1 while old_ai != 0x00: new_ai = atcmd('AI') if new_ai != old_ai: print("ATAI=0x%02X (%s)" % (new_ai, ai_desc.get(new_ai, 'UNKNOWN'))) old_ai = new_ai else: sleep(0.01) def get_temp(): temp_pin = ADC("D0") temp_raw = temp_pin.read() print("Raw pin reading: %d" % temp_raw) # convert temperature to proper units temperatureC = (int((temp_raw * (2500/4096)) - 500) / 10) print("Temperature: %d Celsius" % temperatureC) temperatureF = (temperatureC * 9.0 / 5.0) + 32.0; print("Temperature: %d Fahrenheit" % temperatureF) return temperatureF def send_DRM(stream, datapoint, connection): # send data points to DRM print("posting data...", end ="") try: status = connection.add_datapoint(stream, datapoint) # post data to Device Cloud print("done") print('posted to stream:', stream_id, '| data:', datapoint, '| status:', status.status_code) except Exception as e: print('\r\nexception:', e) def connect_DRM(stream, info, connection): # update data feed info print("updating stream info...", end ="") try: connection.update_datastream(stream, info) print("done") except Exception as e: status = type(e).__name__ + ': ' + str(e) print('\r\nexception:', e) print("checking connection...") watch_ai() print("connected") # Main Program print("checking connection...") watch_ai() print("connected") # create a connection rm = RemoteManagerConnection(credentials=credentials) connect_DRM(stream_id, stream_info, rm) while True: tempF = get_temp() # read temperature value & print to debug send_DRM(stream_id, round(tempF), rm) # send data point to DRM sleep(heartbeat_wait) #wait between cycles |
High and lows
The heartbeat program quieted down our data use and saved us some bandwidth. However it risks missing important events that happen between updates. Edge computing will help solve that problem. For example, let’s say that our goal is to keep temperature within a certain range, maybe between 65 and 80 degrees F. As long as the temperature is within that range, the heartbeat data maintains a historical record and confirms that the sensor is online and functioning. During events of interest, however, we want much faster sampling to create high-resolution data of everything that happened during the event. By defining a range, we eliminate spending on low-value data, while prioritizing high-value event data. This increases our return on investment for the entire system.
Range filtering is easy to do. Every time we read a temperature, we check if it’s outside of the defined range. If it is, we send that data right away. If the temperature is within the safe range, we only send one “heartbeat” reading every 15 minutes. This gives us lots of data when it matters and only a trickle when things are fine. We are already starting to use bandwidth more intelligently.
Copy the below code to a text editor like Notepad. Be sure to enter your own phone number, replacing “your_mobile_number_here” before uploading the code. You can also change the wait_time, heartbeat_wait, min_temp and max_temp variables, if desired.
SMS FILTERING CODE
# Digi XBee3 Cellular Heartbeat Filtered Example # uses a TMP36 to measure temperature and send it as an SMS message # by default repeating once per 15 minutes, continuously # ENTER YOUR PHONE NUMBER, REPLACING "your_mobile_number_here" BEFORE UPLOADING THIS CODE! import network from machine import ADC from time import sleep, time, ticks_ms, ticks_diff wait_time = 60 # seconds between measurements heartbeat_wait = 900 # seconds between default transmissions min_temp = 65 # temperature in F, fliter values above max_temp = 80 # temperature in F, filter values below number = "your_mobile_number_here" # this phone number receives the text notifications last_send = 0 # track heartbeat times def get_temp(): temp_pin = ADC("D0") temp_raw = temp_pin.read() print("Raw pin reading: %d" % temp_raw) # convert temperature to proper units temperatureC = (int((temp_raw * (2500/4096)) - 500) / 10) print("Temperature: %d Celsius" % temperatureC) temperatureF = (temperatureC * 9.0 / 5.0) + 32.0; print("Temperature: %d Fahrenheit" % temperatureF) return temperatureF def send_SMS(phone_number, payload): c = network.Cellular() while not c.isconnected(): print("waiting for cell network...") sleep(1.5) # Pause 1.5 seconds between checking connection print("connected to cell network.") try: c.sms_send(phone_number, payload) print("message sent successfully to " + phone_number) except Exception as e: print("Send failure: " + str(e)) # main Program print("Min temp %d, max temp %d" % (min_temp,max_temp)) while True: tempF = get_temp() # read temperature value & print to debug if (tempF > max_temp or tempF < min_temp): # only send high or low readings message = ("Temperature Alert: %d'F" % tempF) # Message to send out send_SMS(number, message) # send as text to phone number if ( (heartbeat_wait * 1000 < ticks_diff( ticks_ms(), last_send )) or last_send==0 ): # send heartbeat value message = ("Temperature: %d'F" % tempF) # Message to send out last_send = ticks_ms() send_SMS(number, message) # send as text to phone number sleep(wait_time) #wait between cycles |
Once the code has been edited by adding your mobile phone number, it can be uploaded in XCTU at the MicroPython Terminal in the usual way: Ctrl+F, paste, Ctrl+D, then Ctrl+R to run your code.
USE IT
With the filtering example running with settings at their default, you’ll get one message every 15 minutes when the temperature is between 65 and 80 degrees F. If the temperature goes higher or lower, you’ll get a text every minute until it’s back in the safe range. If that’s too many texts, don’t worry. We’ll improve this behavior in the next example. The results on your phone should look like this:
To run the same example, posting to Digi Remote Manager, use the following code.
Digi Remote Manager Filtering Code
# Digi XBee3 Cellular Heartbeat Filtered Example # uses a TMP36 to measure temperature and send it to Digi Remote Manager # by default repeating once every 15 minutes, continuously # ENTER YOUR DRM username and password, REPLACING "your_username_here" etc. BEFORE UPLOADING THIS CODE! from machine import ADC from time import sleep, time, ticks_ms, ticks_diff from remotemanager import RemoteManagerConnection from xbee import atcmd wait_time = 60 # seconds between measurements heartbeat_wait = 900 # seconds between default measurements username = 'your_username_here' #enter your username! password = 'your_password_here' #enter your password! min_temp = 65 # temperature in F, fliter values above max_temp = 80 # temperature in F, filter values below last_send = 0 # track heartbeat times # Device Cloud connection info stream_id = 'temperature' stream_type = 'FLOAT' stream_units = 'degrees F' description = "temperature example" # prepare for connection credentials = {'username': username, 'password': password} stream_info = {"description": description, "id": stream_id, "type": stream_type, "units": stream_units} ai_desc = { 0x00: 'CONNECTED', 0x22: 'REGISTERING_TO_NETWORK', 0x23: 'CONNECTING_TO_INTERNET', 0x24: 'RECOVERY_NEEDED', 0x25: 'NETWORK_REG_FAILURE', 0x2A: 'AIRPLANE_MODE', 0x2B: 'USB_DIRECT', 0x2C: 'PSM_DORMANT', 0x2F: 'BYPASS_MODE_ACTIVE', 0xFF: 'MODEM_INITIALIZING', } def watch_ai(): old_ai = -1 while old_ai != 0x00: new_ai = atcmd('AI') if new_ai != old_ai: print("ATAI=0x%02X (%s)" % (new_ai, ai_desc.get(new_ai, 'UNKNOWN'))) old_ai = new_ai else: sleep(0.01) def get_temp(): temp_pin = ADC("D0") temp_raw = temp_pin.read() print("Raw pin reading: %d" % temp_raw) # convert temperature to proper units temperatureC = (int((temp_raw * (2500/4096)) - 500) / 10) print("Temperature: %d Celsius" % temperatureC) temperatureF = (temperatureC * 9.0 / 5.0) + 32.0; print("Temperature: %d Fahrenheit" % temperatureF) return temperatureF def send_DRM(stream, datapoint, connection): # send data points to DRM print("posting data...", end ="") try: status = connection.add_datapoint(stream, datapoint) # post data to Device Cloud print("done") print('posted to stream:', stream_id, '| data:', datapoint, '| status:', status.status_code) except Exception as e: print('\r\nexception:', e) def connect_DRM(stream, info, connection): # update data feed info print("updating stream info...", end ="") try: connection.update_datastream(stream, info) print("done") except Exception as e: status = type(e).__name__ + ': ' + str(e) print('\r\nexception:', e) print("checking connection...") watch_ai() print("connected") # Main Program print("checking connection...") watch_ai() print("connected") # create a connection rm = RemoteManagerConnection(credentials=credentials) connect_DRM(stream_id, stream_info, rm) print("Min temp %d, max temp %d" % (min_temp,max_temp)) while True: tempF = get_temp() # read temperature value & print to debug if (tempF > max_temp or tempF < min_temp): # only send high or low readings send_DRM(stream_id, round(tempF), rm) # send data point to DRM if ( (heartbeat_wait * 1000 < ticks_diff( ticks_ms(), last_send )) or last_send==0 ): # send heartbeat value last_send = ticks_ms() send_DRM(stream_id, round(tempF), rm) # send data point to DRM sleep(wait_time) #wait between cycles |
Suppress Duplicates
Filtering saved us a lot of bandwidth between interesting events. However you may have noticed that during events, when the temperature is outside of the defined safe range, we’re sending a lot of duplicate readings. If the temperature stabilizes at 85 degrees F, our device will spend its battery and our money pointlessly sending the same value over and over again. Certainly not what we want. We can use a lot less bandwidth by eliminating these duplicates. In fact, we can suppress not only exact duplicates, but also any unimportant minor changes. Remember that our “heartbeat” data will still come through, confirming our device is fully operational.
The code below sends heartbeat data every 15 minutes by default. It also checks the temperature every 10 seconds, but only sends additional data when readings are outside of the safe range AND when they differ from the last data point sent by at least 2 degrees. You can change those defaults by changing the values at the beginning of the code.
Copy the below code to a text editor like Notepad. Be sure to enter your own phone number, replacing “your_mobile_number_here” before uploading the code.
SMS DEDUPLICATION CODE
# Digi XBee3 Cellular Filtered De-duplication Example # uses a TMP36 to measure temperature and send it as an SMS message # by default repeating once per 15 minutes, continuously # ENTER YOUR PHONE NUMBER, REPLACING "your_mobile_number_here" BEFORE UPLOADING THIS CODE! import network from machine import ADC from time import sleep, time, ticks_ms, ticks_diff wait_time = 10 # seconds between measurements heartbeat_wait = 900 # seconds between default transmissions min_temp = 65 # temperature in F, fliter values above max_temp = 80 # temperature in F, filter values below min_diff = 3 # temperature difference in F, triggers immediate send number = "your_mobile_number_here" # this phone number receives the text notifications last_send = 0 # track heartbeat times def get_temp(): temp_pin = ADC("D0") temp_raw = temp_pin.read() print("Raw pin reading: %d" % temp_raw) # convert temperature to proper units temperatureC = (int((temp_raw * (2500/4096)) - 500) / 10) print("Temperature: %d Celsius" % temperatureC) temperatureF = (temperatureC * 9.0 / 5.0) + 32.0; print("Temperature: %d Fahrenheit" % temperatureF) return temperatureF def send_SMS(phone_number, payload): c = network.Cellular() while not c.isconnected(): print("waiting for cell network...") sleep(1.5) # Pause 1.5 seconds between checking connection print("connected to cell network.") try: c.sms_send(phone_number, payload) print("message sent successfully to " + phone_number) except Exception as e: print("Send failure: " + str(e)) # main Program print("Min temp %d, max temp %d" % (min_temp,max_temp)) last_alert = get_temp() # record initial value for comparison while True: tempF = get_temp() # read temperature value & print to debug if (tempF > max_temp or tempF < min_temp): # only send high or low readings if (abs(tempF-last_alert) >= min_diff): message = ("Temperature Alert: %d'F" % tempF) # Message to send out send_SMS(number, message) # send as text to phone number last_alert = tempF if ( (heartbeat_wait * 1000 < ticks_diff( ticks_ms(), last_send )) or last_send==0 ): # send heartbeat value message = ("Temperature: %d'F" % tempF) # Message to send out last_send = ticks_ms() send_SMS(number, message) # send as text to phone number sleep(wait_time) #wait between cycles |
Once the code has been edited by adding your mobile phone number, it can be uploaded in XCTU at the MicroPython Terminal in the usual way. (Ctrl+F, paste, Ctrl+D, then Ctrl+R to run your code.)
USE IT
With the duplicate suppression example running with default values, you’ll get one message every 15 minutes when the temperature is between 65 and 80 degrees F. If the temperature goes higher or lower, you’ll get a text every time the temperature changes by at least three degrees until it’s back in the safe range. The results on your phone should look like this:
To run the same example, posting to Digi Remote Manager, use the following code.
Digi Remote Manager De-duplication Example
# Digi XBee3 Cellular Heartbeat Filtered Example # uses a TMP36 to measure temperature and send it to Digi Remote Manager # by default repeating once every 15 minutes, continuously # ENTER YOUR DRM username and password, REPLACING "your_username_here" etc. BEFORE UPLOADING THIS CODE! from machine import ADC from time import sleep, time, ticks_ms, ticks_diff from remotemanager import RemoteManagerConnection from xbee import atcmd wait_time = 10 # seconds between measurements heartbeat_wait = 900 # seconds between default measurements username = 'your_username_here' #enter your username! password = 'your_password_here' #enter your password! min_temp = 65 # temperature in F, fliter values above max_temp = 80 # temperature in F, filter values below min_diff = 2 # temperature difference in F, triggers immediate send last_send = 0 # track heartbeat times # Device Cloud connection info stream_id = 'temperature' stream_type = 'FLOAT' stream_units = 'degrees F' description = "temperature example" # prepare for connection credentials = {'username': username, 'password': password} stream_info = {"description": description, "id": stream_id, "type": stream_type, "units": stream_units} ai_desc = { 0x00: 'CONNECTED', 0x22: 'REGISTERING_TO_NETWORK', 0x23: 'CONNECTING_TO_INTERNET', 0x24: 'RECOVERY_NEEDED', 0x25: 'NETWORK_REG_FAILURE', 0x2A: 'AIRPLANE_MODE', 0x2B: 'USB_DIRECT', 0x2C: 'PSM_DORMANT', 0x2F: 'BYPASS_MODE_ACTIVE', 0xFF: 'MODEM_INITIALIZING', } def watch_ai(): old_ai = -1 while old_ai != 0x00: new_ai = atcmd('AI') if new_ai != old_ai: print("ATAI=0x%02X (%s)" % (new_ai, ai_desc.get(new_ai, 'UNKNOWN'))) old_ai = new_ai else: sleep(0.01) def get_temp(): temp_pin = ADC("D0") temp_raw = temp_pin.read() print("Raw pin reading: %d" % temp_raw) # convert temperature to proper units temperatureC = (int((temp_raw * (2500/4096)) - 500) / 10) print("Temperature: %d Celsius" % temperatureC) temperatureF = (temperatureC * 9.0 / 5.0) + 32.0; print("Temperature: %d Fahrenheit" % temperatureF) return temperatureF def send_DRM(stream, datapoint, connection): # send data points to DRM print("posting data...", end ="") try: status = connection.add_datapoint(stream, datapoint) # post data to Device Cloud print("done") print('posted to stream:', stream_id, '| data:', datapoint, '| status:', status.status_code) except Exception as e: print('\r\nexception:', e) def connect_DRM(stream, info, connection): # update data feed info print("updating stream info...", end ="") try: connection.update_datastream(stream, info) print("done") except Exception as e: status = type(e).__name__ + ': ' + str(e) print('\r\nexception:', e) print("checking connection...") watch_ai() print("connected") # Main Program print("checking connection...") watch_ai() print("connected") # create a connection rm = RemoteManagerConnection(credentials=credentials) connect_DRM(stream_id, stream_info, rm) print("Min temp %d, max temp %d" % (min_temp,max_temp)) last_alert = get_temp() # record initial value for comparison while True: tempF = get_temp() # read temperature value & print to debug if (tempF > max_temp or tempF < min_temp): # only send high or low readings if (abs(tempF-last_alert) >= min_diff): send_DRM(stream_id, round(tempF), rm) # send data point to DRM last_alert = tempF if ( (heartbeat_wait * 1000 < ticks_diff( ticks_ms(), last_send )) or last_send==0 ): # send heartbeat value last_send = ticks_ms() send_DRM(stream_id, round(tempF), rm) # send data point to DRM sleep(wait_time) #wait between cycles |
Summary
These examples show a few powerful ways to improve your IoT system’s bandwidth and battery use. We created a minimum “heartbeat” of data that’s sent relatively infrequently to reduce bandwidth costs. Then we defined a “safe” range and added some code to send data immediately and frequently during important high and low temperature events. Finally, we removed redundant duplicate data and minor changes that are not of interest. The results are a system that sends minimal data when things are fine, and immediately transmits just the urgent data that matters. That drastically reduces our data costs as well as our storage costs. When combined with low-power sleep modes, it will also greatly improve battery life. Look for power management examples in future posts.
Filtering and de-duplication are examples of the many useful bandwidth reduction strategies. Upcoming examples will demonstrate how to aggregate and analyze data prior to transmission, further improving your ROI.
Source: https://www.digi.com/blog/micropython-examples-for-edge-computing-part-3-bandwidth-management/