ecopipeline 0.7.6__tar.gz → 0.7.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ecopipeline-0.7.6/src/ecopipeline.egg-info → ecopipeline-0.7.7}/PKG-INFO +1 -1
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/setup.cfg +1 -1
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline/extract/__init__.py +2 -2
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline/extract/extract.py +85 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline/utils/ConfigManager.py +37 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7/src/ecopipeline.egg-info}/PKG-INFO +1 -1
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/LICENSE +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/README.md +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/pyproject.toml +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/setup.py +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline/__init__.py +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline/load/__init__.py +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline/load/load.py +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline/transform/__init__.py +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline/transform/bayview.py +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline/transform/lbnl.py +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline/transform/transform.py +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline/utils/__init__.py +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline/utils/unit_convert.py +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline.egg-info/SOURCES.txt +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline.egg-info/dependency_links.txt +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline.egg-info/requires.txt +0 -0
- {ecopipeline-0.7.6 → ecopipeline-0.7.7}/src/ecopipeline.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = ecopipeline
|
|
3
|
-
version = 0.7.
|
|
3
|
+
version = 0.7.7
|
|
4
4
|
authors = ["Carlos Bello, <bellocarlos@seattleu.edu>, Emil Fahrig <fahrigemil@seattleu.edu>, Casey Mang <cmang@seattleu.edu>, Julian Harris <harrisjulian@seattleu.edu>, Roger Tram <rtram@seattleu.edu>, Nolan Price <nolan@ecotope.com>"]
|
|
5
5
|
description = Contains functions for use in Ecotope Datapipelines
|
|
6
6
|
long_description = file: README.md
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from .extract import get_noaa_data, json_to_df, extract_files, get_last_full_day_from_db, get_db_row_from_time, extract_new, csv_to_df, get_sub_dirs, msa_to_df, fm_api_to_df, small_planet_control_to_df, dent_csv_to_df, flow_csv_to_df, pull_egauge_data, egauge_csv_to_df, remove_char_sequence_from_csv_header
|
|
1
|
+
from .extract import get_noaa_data, json_to_df, extract_files, get_last_full_day_from_db, get_db_row_from_time, extract_new, csv_to_df, get_sub_dirs, msa_to_df, fm_api_to_df, small_planet_control_to_df, dent_csv_to_df, flow_csv_to_df, pull_egauge_data, egauge_csv_to_df, remove_char_sequence_from_csv_header, tb_api_to_df
|
|
2
2
|
__all__ = ["get_noaa_data", "json_to_df", "extract_files", "get_last_full_day_from_db", "get_db_row_from_time", 'extract_new', "csv_to_df", "get_sub_dirs", "msa_to_df", "fm_api_to_df",
|
|
3
|
-
"small_planet_control_to_df","dent_csv_to_df","flow_csv_to_df","pull_egauge_data", "egauge_csv_to_df","remove_char_sequence_from_csv_header"]
|
|
3
|
+
"small_planet_control_to_df","dent_csv_to_df","flow_csv_to_df","pull_egauge_data", "egauge_csv_to_df","remove_char_sequence_from_csv_header", "tb_api_to_df"]
|
|
@@ -14,6 +14,7 @@ from pytz import timezone, utc
|
|
|
14
14
|
import mysql.connector.errors as mysqlerrors
|
|
15
15
|
import requests
|
|
16
16
|
import subprocess
|
|
17
|
+
import traceback
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def get_last_full_day_from_db(config : ConfigManager, table_identifier : str = "minute") -> datetime:
|
|
@@ -751,6 +752,90 @@ def pull_egauge_data(config: ConfigManager, eGauge_ids: list, eGauge_usr : str,
|
|
|
751
752
|
print(f"Could not download new data from eGauge device: {e}")
|
|
752
753
|
|
|
753
754
|
os.chdir(original_directory)
|
|
755
|
+
|
|
756
|
+
def tb_api_to_df(config: ConfigManager, startTime: datetime = None, endTime: datetime = None):
|
|
757
|
+
if endTime is None:
|
|
758
|
+
endTime = datetime.now()
|
|
759
|
+
if startTime is None:
|
|
760
|
+
# 28 hours to ensure encapsulation of last day
|
|
761
|
+
startTime = endTime - timedelta(hours=28)
|
|
762
|
+
|
|
763
|
+
if endTime - timedelta(hours=4) > startTime:
|
|
764
|
+
time_diff = endTime - startTime
|
|
765
|
+
midpointTime = startTime + time_diff / 2
|
|
766
|
+
# recursively construct the df
|
|
767
|
+
df_1 = tb_api_to_df(config, startTime, midpointTime)
|
|
768
|
+
df_2 = tb_api_to_df(config, midpointTime, endTime)
|
|
769
|
+
df = pd.concat([df_1, df_2])
|
|
770
|
+
df = df.sort_index()
|
|
771
|
+
df = df.groupby(df.index).mean()
|
|
772
|
+
return df
|
|
773
|
+
|
|
774
|
+
url = f'https://thingsboard.cloud/api/plugins/telemetry/DEVICE/{config.api_device_id}/values/timeseries'
|
|
775
|
+
token = config.get_thingsboard_token()
|
|
776
|
+
keys = _get_tb_keys(config, token)
|
|
777
|
+
if len(keys) <= 0:
|
|
778
|
+
raise Exception(f"No sensors available at ThingsBoard site with id {config.api_device_id}")
|
|
779
|
+
key_string = ','.join(keys)
|
|
780
|
+
params = {
|
|
781
|
+
'keys': key_string,
|
|
782
|
+
'startTs': f'{int(startTime.timestamp())*1000}',
|
|
783
|
+
'endTs': f'{int(endTime.timestamp())*1000}',
|
|
784
|
+
'orderBy': 'ASC',
|
|
785
|
+
'useStrictDataTypes': 'false'
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
# Headers
|
|
789
|
+
headers = {
|
|
790
|
+
'accept': 'application/json',
|
|
791
|
+
'X-Authorization': f'Bearer {token}'
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
try:
|
|
795
|
+
response = requests.get(url, headers=headers, params=params)
|
|
796
|
+
if response.status_code == 200:
|
|
797
|
+
response_json = response.json()
|
|
798
|
+
data = {}
|
|
799
|
+
for key, records in response_json.items():
|
|
800
|
+
try:
|
|
801
|
+
series = pd.Series(
|
|
802
|
+
data={record['ts']: float(record['value']) for record in records}
|
|
803
|
+
)
|
|
804
|
+
data[key] = series
|
|
805
|
+
except:
|
|
806
|
+
print_statement = f"Could not convert {key} values to floats."
|
|
807
|
+
# print(print_statement)
|
|
808
|
+
df = pd.DataFrame(data)
|
|
809
|
+
df.index = pd.to_datetime(df.index, unit='ms')
|
|
810
|
+
df = df.sort_index()
|
|
811
|
+
return df
|
|
812
|
+
|
|
813
|
+
print(f"Failed to make GET request. Status code: {response.status_code} {response.json()}")
|
|
814
|
+
return pd.DataFrame()
|
|
815
|
+
except Exception as e:
|
|
816
|
+
traceback.print_exc()
|
|
817
|
+
print(f"An error occurred: {e}")
|
|
818
|
+
return pd.DataFrame()
|
|
819
|
+
|
|
820
|
+
def _get_tb_keys(config: ConfigManager, token : str) -> List[str]:
|
|
821
|
+
url = f'https://thingsboard.cloud/api/plugins/telemetry/DEVICE/{config.api_device_id}/keys/timeseries'
|
|
822
|
+
|
|
823
|
+
# Headers
|
|
824
|
+
headers = {
|
|
825
|
+
'accept': 'application/json',
|
|
826
|
+
'X-Authorization': f'Bearer {token}'
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
try:
|
|
830
|
+
response = requests.get(url, headers=headers)
|
|
831
|
+
if response.status_code == 200:
|
|
832
|
+
return response.json()
|
|
833
|
+
|
|
834
|
+
print(f"Failed to make GET request. Status code: {response.status_code} {response.json()}")
|
|
835
|
+
return []
|
|
836
|
+
except Exception as e:
|
|
837
|
+
print(f"An error occurred: {e}")
|
|
838
|
+
return []
|
|
754
839
|
|
|
755
840
|
def get_sub_dirs(dir: str) -> List[str]:
|
|
756
841
|
"""
|
|
@@ -64,10 +64,16 @@ class ConfigManager:
|
|
|
64
64
|
self.data_directory = configure.get('data', 'directory')
|
|
65
65
|
configured_data_method = True
|
|
66
66
|
if 'fieldManager_api_usr' in configure['data'] and 'fieldManager_api_pw' in configure['data'] and 'fieldManager_device_id' in configure['data']:
|
|
67
|
+
# LEGACY, Remove when you can
|
|
67
68
|
self.api_usr = configure.get('data', 'fieldManager_api_usr')
|
|
68
69
|
self.api_pw = configure.get('data', 'fieldManager_api_pw')
|
|
69
70
|
self.api_device_id = configure.get('data','fieldManager_device_id')
|
|
70
71
|
configured_data_method = True
|
|
72
|
+
elif 'api_usr' in configure['data'] and 'api_pw' in configure['data'] and 'device_id' in configure['data']:
|
|
73
|
+
self.api_usr = configure.get('data', 'api_usr')
|
|
74
|
+
self.api_pw = configure.get('data', 'api_pw')
|
|
75
|
+
self.api_device_id = configure.get('data','device_id')
|
|
76
|
+
configured_data_method = True
|
|
71
77
|
if not configured_data_method:
|
|
72
78
|
raise Exception('data configuration section missing or incomplete in configuration file.')
|
|
73
79
|
|
|
@@ -205,6 +211,7 @@ class ConfigManager:
|
|
|
205
211
|
return connection, connection.cursor()
|
|
206
212
|
|
|
207
213
|
def get_fm_token(self) -> str:
|
|
214
|
+
# for getting feild manager api token
|
|
208
215
|
if self.api_usr is None or self.api_pw is None:
|
|
209
216
|
raise Exception("Cannot retrieve Field Manager API token. Credentials were not provided in configuration file.")
|
|
210
217
|
url = f"https://www.fieldpop.io/rest/login?username={self.api_usr}&password={self.api_pw}"
|
|
@@ -221,6 +228,36 @@ class ConfigManager:
|
|
|
221
228
|
print(f"An error occurred: {e}")
|
|
222
229
|
return None
|
|
223
230
|
|
|
231
|
+
def get_thingsboard_token(self) -> str:
|
|
232
|
+
# for getting ThingsBoard api token
|
|
233
|
+
if self.api_usr is None or self.api_pw is None:
|
|
234
|
+
raise Exception("Cannot retrieve ThingsBoard API token. Credentials were not provided in configuration file.")
|
|
235
|
+
url = 'https://thingsboard.cloud/api/auth/login'
|
|
236
|
+
|
|
237
|
+
# Request payload (data to send in the POST)
|
|
238
|
+
payload = {
|
|
239
|
+
'username': self.api_usr,
|
|
240
|
+
'password': self.api_pw
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
# Headers
|
|
244
|
+
headers = {
|
|
245
|
+
'Content-Type': 'application/json',
|
|
246
|
+
'Accept': 'application/json'
|
|
247
|
+
}
|
|
248
|
+
try:
|
|
249
|
+
response = requests.post(url, json=payload, headers=headers)
|
|
250
|
+
# Check if the request was successful (status code 200)
|
|
251
|
+
if response.status_code == 200:
|
|
252
|
+
response = response.json() # Return the response data as JSON
|
|
253
|
+
return response['token']
|
|
254
|
+
else:
|
|
255
|
+
print(f"Failed to make GET request. Status code: {response.status_code}")
|
|
256
|
+
return None
|
|
257
|
+
except Exception as e:
|
|
258
|
+
print(f"An error occurred: {e}")
|
|
259
|
+
return None
|
|
260
|
+
|
|
224
261
|
def get_fm_device_id(self) -> str:
|
|
225
262
|
if self.api_device_id is None:
|
|
226
263
|
raise Exception("Field Manager device ID has not been configured.")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|