ecopipeline 0.7.5__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.5/src/ecopipeline.egg-info → ecopipeline-0.7.7}/PKG-INFO +1 -1
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/setup.cfg +1 -1
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline/extract/__init__.py +2 -2
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline/extract/extract.py +92 -1
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline/utils/ConfigManager.py +37 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7/src/ecopipeline.egg-info}/PKG-INFO +1 -1
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/LICENSE +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/README.md +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/pyproject.toml +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/setup.py +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline/__init__.py +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline/load/__init__.py +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline/load/load.py +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline/transform/__init__.py +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline/transform/bayview.py +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline/transform/lbnl.py +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline/transform/transform.py +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline/utils/__init__.py +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline/utils/unit_convert.py +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline.egg-info/SOURCES.txt +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline.egg-info/dependency_links.txt +0 -0
- {ecopipeline-0.7.5 → ecopipeline-0.7.7}/src/ecopipeline.egg-info/requires.txt +0 -0
- {ecopipeline-0.7.5 → 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:
|
|
@@ -151,11 +152,17 @@ def extract_new(startTime: datetime, filenames: List[str], decihex = False, time
|
|
|
151
152
|
|
|
152
153
|
|
|
153
154
|
else:
|
|
155
|
+
endTime_int = endTime
|
|
154
156
|
if epochFormat:
|
|
155
157
|
startTime_int = int(startTime.timestamp())
|
|
158
|
+
if not endTime is None:
|
|
159
|
+
endTime_int = int(endTime.timestamp())
|
|
156
160
|
else:
|
|
157
161
|
startTime_int = int(startTime.strftime(dateFormat))
|
|
158
|
-
|
|
162
|
+
if not endTime is None:
|
|
163
|
+
endTime_int = int(endTime.strftime(dateFormat)
|
|
164
|
+
)
|
|
165
|
+
return_list = list(filter(lambda filename: int(filename[dateStringStartIdx:dateStringEndIdx]) >= startTime_int and (endTime_int is None or int(filename[dateStringStartIdx:dateStringEndIdx]) < endTime_int), filenames))
|
|
159
166
|
return return_list
|
|
160
167
|
|
|
161
168
|
def extract_files(extension: str, config: ConfigManager, data_sub_dir : str = "", file_prefix : str = "") -> List[str]:
|
|
@@ -745,6 +752,90 @@ def pull_egauge_data(config: ConfigManager, eGauge_ids: list, eGauge_usr : str,
|
|
|
745
752
|
print(f"Could not download new data from eGauge device: {e}")
|
|
746
753
|
|
|
747
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 []
|
|
748
839
|
|
|
749
840
|
def get_sub_dirs(dir: str) -> List[str]:
|
|
750
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
|