""" Convert data length to radio TX/RX energy. Contains classes for some embedded CPUs/MCUs. Given a configuration, each class can convert a cycle count to an energy consumption. """ import numpy as np def get_class(radio_name: str): """Return model class for radio_name.""" if radio_name == 'CC1200tx': return CC1200tx if radio_name == 'CC1200rx': return CC1200rx if radio_name == 'NRF24L01tx': return NRF24L01tx if radio_name == 'NRF24L01dtx': return NRF24L01dtx if radio_name == 'esp8266dtx': return ESP8266dtx if radio_name == 'esp8266drx': return ESP8266drx def _param_list_to_dict(device, param_list): param_dict = dict() for i, parameter in enumerate(sorted(device.parameters.keys())): param_dict[parameter] = param_list[i] return param_dict class CC1200tx: """CC1200 TX energy based on aemr measurements.""" name = 'CC1200tx' parameters = { 'symbolrate' : [6, 12, 25, 50, 100, 200, 250], # ksps 'txbytes' : [], 'txpower' : [10, 20, 30, 40, 47], # dBm = f(txpower) } default_params = { 'symbolrate' : 100, 'txpower' : 47, } @staticmethod def get_energy(params: dict): if type(params) != dict: return CC1200tx.get_energy(_param_list_to_dict(CC1200tx, params)) # Mittlere TX-Leistung, gefitted von AEMR # Messdaten erhoben bei 3.6V power = 8.18053941e+04 power -= 1.24208376e+03 * np.sqrt(params['symbolrate']) power -= 5.73742779e+02 * np.log(params['txbytes']) power += 1.76945886e+01 * (params['txpower'])**2 power += 2.33469617e+02 * np.sqrt(params['symbolrate']) * np.log(params['txbytes']) power -= 6.99137635e-01 * np.sqrt(params['symbolrate']) * (params['txpower'])**2 power -= 3.31365158e-01 * np.log(params['txbytes']) * (params['txpower'])**2 power += 1.32784945e-01 * np.sqrt(params['symbolrate']) * np.log(params['txbytes']) * (params['txpower'])**2 # txDone-Timeout, gefitted von AEMR duration = 3.65513500e+02 duration += 8.01016526e+04 * 1/(params['symbolrate']) duration -= 7.06364515e-03 * params['txbytes'] duration += 8.00029860e+03 * 1/(params['symbolrate']) * params['txbytes'] # TX-Energie, gefitted von AEMR # Achtung: Energy ist in µJ, nicht (wie in AEMR-Transitionsmodellen üblich) in pJ # Messdaten erhoben bei 3.6V energy = 1.74383259e+01 energy += 6.29922138e+03 * 1/(params['symbolrate']) energy += 1.13307135e-02 * params['txbytes'] energy -= 1.28121377e-04 * (params['txpower'])**2 energy += 6.29080184e+02 * 1/(params['symbolrate']) * params['txbytes'] energy += 1.25647926e+00 * 1/(params['symbolrate']) * (params['txpower'])**2 energy += 1.31996202e-05 * params['txbytes'] * (params['txpower'])**2 energy += 1.25676966e-01 * 1/(params['symbolrate']) * params['txbytes'] * (params['txpower'])**2 return energy * 1e-6 @staticmethod def get_energy_per_byte(params): A = 8.18053941e+04 A -= 1.24208376e+03 * np.sqrt(params['symbolrate']) A += 1.76945886e+01 * (params['txpower'])**2 A -= 6.99137635e-01 * np.sqrt(params['symbolrate']) * (params['txpower'])**2 B = -5.73742779e+02 B += 2.33469617e+02 * np.sqrt(params['symbolrate']) B -= 3.31365158e-01 * (params['txpower'])**2 B += 1.32784945e-01 * np.sqrt(params['symbolrate']) * (params['txpower'])**2 C = 3.65513500e+02 C += 8.01016526e+04 * 1/(params['symbolrate']) D = -7.06364515e-03 D += 8.00029860e+03 * 1/(params['symbolrate']) x = params['txbytes'] # in pJ de_dx = A * D + B * C * 1/x + B * D * (np.log(x) + 1) # in µJ de_dx = 1.13307135e-02 de_dx += 6.29080184e+02 * 1/(params['symbolrate']) de_dx += 1.31996202e-05 * (params['txpower'])**2 de_dx += 1.25676966e-01 * 1/(params['symbolrate']) * (params['txpower'])**2 #de_dx = (B * 1/x) * (C + D * x) + (A + B * np.log(x)) * D return de_dx * 1e-6 class CC1200rx: """CC1200 RX energy based on aemr measurements.""" name = 'CC1200rx' parameters = { 'symbolrate' : [6, 12, 25, 50, 100, 200, 250], # ksps 'txbytes' : [], 'txpower' : [10, 20, 30, 40, 47], # dBm = f(txpower) } default_params = { 'symbolrate' : 100, 'txpower' : 47, } @staticmethod def get_energy(params): # TODO return params['txbytes'] * CC1200rx.get_energy_per_byte(params) @staticmethod def get_energy_per_byte(params): #RX : 0 + regression_arg(0) + regression_arg(1) * np.log(parameter(symbolrate) + 1) # [84414.91636169 205.63323036] de_dx = (84414.91636169 + 205.63323036 * np.log(params['symbolrate'] + 1)) * 8000 / params['symbolrate'] return de_dx * 1e-12 class NRF24L01rx: """NRF24L01+ RX energy based on aemr measurements (using variable packet size)""" name = 'NRF24L01' parameters = { 'datarate' : [250, 1000, 2000], # kbps 'txbytes' : [], 'txpower' : [-18, -12, -6, 0], # dBm 'voltage' : [1.9, 3.6], } default_params = { 'datarate' : 1000, 'txpower' : -6, 'voltage' : 3, } @staticmethod def get_energy_per_byte(params): # RX : 0 + regression_arg(0) + regression_arg(1) * np.sqrt(parameter(datarate)) # [48530.73235537 117.25274402] de_dx = (48530.73235537 + 117.25274402 * np.sqrt(params['datarate'])) * 8000 / params['datarate'] return de_dx * 1e-12 # PYTHONPATH=lib bin/analyze-archive.py --show-model=all --show-quality=table ../data/*_RF24_no_retries.tar class NRF24L01tx: """NRF24L01+ TX(*) energy based on aemr measurements (32B fixed packet size, (*)ack-await, no retries).""" name = 'NRF24L01' parameters = { 'datarate' : [250, 1000, 2000], # kbps 'txbytes' : [], 'txpower' : [-18, -12, -6, 0], # dBm 'voltage' : [1.9, 3.6], } default_params = { 'datarate' : 1000, 'txpower' : -6, 'voltage' : 3, } # AEMR: # TX power / energy: #TX : 0 + regression_arg(0) + regression_arg(1) * 1/(parameter(datarate)) + regression_arg(2) * (19.47+parameter(txpower))**2 + regression_arg(3) * 1/(parameter(datarate)) * (19.47+parameter(txpower))**2 # [6.30323056e+03 2.59889924e+06 7.82186268e+00 8.69746093e+03] #TX : 0 + regression_arg(0) + regression_arg(1) * 1/(parameter(datarate)) + regression_arg(2) * (19.47+parameter(txpower))**2 + regression_arg(3) * 1/(parameter(datarate)) * (19.47+parameter(txpower))**2 # [7.67932887e+00 1.02969455e+04 4.55116475e-03 2.99786534e+01] #epilogue : timeout : 0 + regression_arg(0) + regression_arg(1) * 1/(parameter(datarate)) # [ 1624.06589147 332251.93798766] @staticmethod def get_energy(params): if type(params) != dict: return NRF24L01tx.get_energy(_param_list_to_dict(NRF24L01tx, params)) # TX-Leistung, gefitted von AEMR # Messdaten erhoben bei 3.6V power = 6.30323056e+03 power += 2.59889924e+06 * 1/params['datarate'] power += 7.82186268e+00 * (19.47+params['txpower'])**2 power += 8.69746093e+03 * 1/params['datarate'] * (19.47+params['txpower'])**2 # TX-Dauer, gefitted von AEMR duration = 1624.06589147 duration += 332251.93798766 * 1/params['datarate'] # TX-Energie, gefitted von AEMR # Achtung: Energy ist in µJ, nicht (wie in AEMR-Transitionsmodellen üblich) in pJ # Messdaten erhoben bei 3.6V energy = 7.67932887e+00 energy += 1.02969455e+04 * 1/params['datarate'] energy += 4.55116475e-03 * (19.47+params['txpower'])**2 energy += 2.99786534e+01 * 1/params['datarate'] * (19.47+params['txpower'])**2 energy = power * 1e-6 * duration * 1e-6 * np.ceil(params['txbytes'] / 32) return energy @staticmethod def get_energy_per_byte(params): if type(params) != dict: return NRF24L01tx.get_energy_per_byte(_param_list_to_dict(NRF24L01tx, params)) # in µJ de_dx = 0 class NRF24L01dtx: """nRF24L01+ TX energy based on datasheet values (probably unerestimated)""" name = 'NRF24L01' parameters = { 'datarate' : [250, 1000, 2000], # kbps 'txbytes' : [], 'txpower' : [-18, -12, -6, 0], # dBm 'voltage' : [1.9, 3.6], } default_params = { 'datarate' : 1000, 'txpower' : -6, 'voltage' : 3, } # 130 us RX settling: 8.9 mE # 130 us TX settling: 8 mA @staticmethod def get_energy(params): if type(params) != dict: return NRF24L01dtx.get_energy(_param_list_to_dict(NRF24L01dtx, params)) header_bytes = 7 # TX settling: 130 us @ 8 mA energy = 8e-3 * params['voltage'] * 130e-6 if params['txpower'] == -18: current = 7e-3 elif params['txpower'] == -12: current = 7.5e-3 elif params['txpower'] == -6: current = 9e-3 elif params['txpower'] == 0: current = 11.3e-3 energy += current * params['voltage'] * ((header_bytes + params['txbytes']) * 8 / (params['datarate'] * 1e3)) return energy class ESP8266dtx: """esp8266 TX energy based on (hardly documented) datasheet values""" name = 'esp8266' parameters = { 'voltage' : [2.5, 3.0, 3.3, 3.6], 'txbytes' : [], 'bitrate' : [65e6], 'tx_current' : [120e-3], } default_params = { 'voltage' : 3, 'bitrate' : 65e6, 'tx_current' : 120e-3 } @staticmethod def get_energy(params): # TODO return 0 @staticmethod def get_energy_per_byte(params): if type(params) != dict: return ESP8266dtx.get_energy_per_byte(_param_list_to_dict(ESP8266dtx, params)) # TX in 802.11n MCS7 -> 64QAM, 65/72.2 Mbit/s @ 20MHz channel, 135/150 Mbit/s @ 40MHz # -> Value for 65 Mbit/s @ 20MHz channel return params['tx_current'] * params['voltage'] / params['bitrate'] class ESP8266drx: """esp8266 RX energy based on (hardly documented) datasheet values""" name = 'esp8266' parameters = { 'voltage' : [2.5, 3.0, 3.3, 3.6], 'txbytes' : [], 'bitrate' : [65e6], 'rx_current' : [56e-3], } default_params = { 'voltage' : 3, 'bitrate' : 65e6, 'rx_current' : 56e-3 } @staticmethod def get_energy(params): # TODO return 0 @staticmethod def get_energy_per_byte(params): if type(params) != dict: return ESP8266drx.get_energy_per_byte(_param_list_to_dict(ESP8266drx, params)) # TX in 802.11n MCS7 -> 64QAM, 65/72.2 Mbit/s @ 20MHz channel, 135/150 Mbit/s @ 40MHz # -> Value for 65 Mbit/s @ 20MHz channel return params['rx_current'] * params['voltage'] / params['bitrate']