| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- import hashlib
- import json
- import multiprocessing
- import os
- import sys
- import time
- from datetime import datetime
- from typing import Dict, List, Tuple
- from urllib.parse import urlparse, parse_qs
- import pandas as pd
- import requests
- class EnosAPIClient:
- TOKEN_CACHE_FILE = "enos_token_cache.json"
- def __init__(self, app_key: str, app_secret: str, apigw_address: str,
- org_id: str, is_edge: bool = True, cache_dir: str = None):
- self.app_key = app_key
- self.app_secret = app_secret
- self.apigw_address = apigw_address
- self.org_id = org_id
- self.protocol = "http" if is_edge else "https"
- self.cache_dir = cache_dir or os.path.join(os.getcwd(), ".enos_cache")
- os.makedirs(self.cache_dir, exist_ok=True)
- self.cache_file = os.path.join(self.cache_dir, self.TOKEN_CACHE_FILE)
- self.access_token = None
- self.token_expire_time = 0
- self._load_token_from_cache()
- def _sha256_lower(self, text: str) -> str:
- return hashlib.sha256(text.encode('utf-8')).hexdigest().lower()
- def _load_token_from_cache(self) -> bool:
- try:
- if not os.path.exists(self.cache_file):
- return False
- with open(self.cache_file, 'r', encoding='utf-8') as f:
- cache_data = json.load(f)
- cache_key = f"{self.app_key}_{self.apigw_address}"
- token_info = cache_data.get(cache_key)
- if token_info and time.time() < token_info.get('expire_time', 0) - 300:
- self.access_token = token_info.get('access_token')
- self.token_expire_time = token_info.get('expire_time')
- return True
- return False
- except:
- return False
- def _save_token_to_cache(self, access_token: str, expire_seconds: int):
- try:
- cache_data = {}
- if os.path.exists(self.cache_file):
- with open(self.cache_file, 'r', encoding='utf-8') as f:
- cache_data = json.load(f)
- cache_key = f"{self.app_key}_{self.apigw_address}"
- cache_data[cache_key] = {
- 'access_token': access_token,
- 'expire_time': time.time() + expire_seconds
- }
- with open(self.cache_file, 'w', encoding='utf-8') as f:
- json.dump(cache_data, f, indent=2)
- except:
- pass
- def get_access_token(self, force_refresh: bool = False) -> Tuple[bool, str]:
- if not force_refresh and self.access_token and time.time() < self.token_expire_time - 300:
- return True, "使用缓存Token"
- timestamp = int(time.time() * 1000)
- encryption = self._sha256_lower(f"{self.app_key}{timestamp}{self.app_secret}")
- url = f"{self.protocol}://{self.apigw_address}/apim-token-service/v2.0/token/get"
- try:
- resp = requests.post(url, json={
- "appKey": self.app_key,
- "encryption": encryption,
- "timestamp": timestamp
- }, timeout=10)
- result = resp.json()
- if result.get("status") == 0:
- token_data = result.get("data", {})
- self.access_token = token_data.get("accessToken")
- expire = token_data.get("expire", 7200)
- self.token_expire_time = time.time() + expire
- self._save_token_to_cache(self.access_token, expire)
- return True, "获取Token成功"
- return False, result.get("msg", "未知错误")
- except Exception as e:
- return False, str(e)
- def _generate_signature(self, url: str, body: str = "") -> Dict:
- parsed = urlparse(url)
- params = parse_qs(parsed.query)
- sorted_keys = sorted(params.keys())
- params_data = "".join([f"{k}{params[k][0]}" for k in sorted_keys if params[k]])
- if body:
- params_data += body
- timestamp = str(int(time.time() * 1000))
- sign_data = f"{self.access_token}{params_data}{timestamp}{self.app_secret}"
- apim_sign = self._sha256_lower(sign_data)
- return {
- "apim-accesstoken": self.access_token,
- "apim-signature": apim_sign,
- "apim-timestamp": timestamp,
- "Content-Type": "application/json; charset=utf-8"
- }
- def call_api(self, url: str, method: str = "GET", body: Dict = None) -> Tuple[bool, Dict]:
- if not self.access_token or time.time() >= self.token_expire_time:
- success, msg = self.get_access_token(force_refresh=True)
- if not success:
- return False, {"error": f"Token获取失败: {msg}"}
- body_str = json.dumps(body, ensure_ascii=False) if body else ""
- headers = self._generate_signature(url, body_str)
- try:
- if method.upper() == "GET":
- resp = requests.get(url, headers=headers, timeout=30)
- else:
- resp = requests.post(url, headers=headers, data=body_str.encode('utf-8'), timeout=30)
- result = resp.json()
- if result.get("code") == 0:
- return True, result
- return False, result
- except Exception as e:
- return False, {"error": str(e)}
- def split_list(lst: List, chunk_size: int) -> List[List]:
- """将列表按指定大小分组"""
- return [lst[i:i + chunk_size] for i in range(0, len(lst), chunk_size)]
- def query_historical_data(client: EnosAPIClient, mdm_id: str, point_ids: List[str],
- start_time: str, end_time: str, interval: str = "RAW", retry_times=0) -> Tuple[
- bool, List[Dict]]:
- """
- 查询历史测点数据(支持测点自动分批)
- """
- all_items = []
- point_groups = split_list(point_ids, 20) # 每20个测点一组
- print(f"共 {len(point_ids)} 个测点,分 {len(point_groups)} 批查询")
- for idx, group in enumerate(point_groups, 1):
- points_str = ",".join(group)
- url = (f"{client.protocol}://{client.apigw_address}/cds-timeseries-service/v1.0/tsdb-detail"
- f"?action=query"
- f"&orgId={client.org_id}"
- f"&mdmIds={mdm_id}"
- f"&pointIdsWithLogic={points_str}"
- f"&startTime={start_time}"
- f"&endTime={end_time}"
- f"&interval={interval}")
- success, result = client.call_api(url, "GET")
- if success:
- items = result.get("data", {}).get("items", [])
- all_items.extend(items)
- print(f"批次 {idx}/{len(point_groups)}:获取 {len(items)} 条记录")
- else:
- if retry_times <= 5:
- retry_times = retry_times + 1
- error_msg = result.get("error", result.get("msg", "未知错误"))
- print(f"批次 {idx}/{len(point_groups)}: 查询失败 - {error_msg},次数: {retry_times}")
- query_historical_data(client, mdm_id, point_ids, start_time, end_time, interval, retry_times)
- time.sleep(0.01)
- else:
- print(f"风机:{mdm_id}, {len(point_groups)}, 查询{retry_times}仍失败")
- return False, []
- return True, all_items
- def get_prev_day(date: str | None | datetime = None, prev_days: int = 1) -> str:
- """
- 获取上一天日期
- :param date: 日期字符串,格式为YYYY-MM-DD
- :param prev_days: 上几天,默认1天
- :return: 上几天日期字符串,格式为YYYY-MM-DD ,例如:2026-06-01
- """
- if not date:
- date = datetime.now().strftime("%Y-%m-%d")
- date = pd.to_datetime(date)
- prev_day = date - pd.Timedelta(days=prev_days)
- return prev_day.strftime("%Y-%m-%d")
- def get_prev_days(date: str | None | datetime = None, prev_days: int = 1) -> list:
- """
- 获取上一天日期
- :param date: 日期字符串,格式为YYYY-MM-DD
- :param prev_days: 上几天,默认1天
- :return: 上几天日期字符串,格式为YYYY-MM-DD ,例如:2026-06-01
- """
- if not date:
- date = datetime.now().strftime("%Y-%m-%d")
- date = pd.to_datetime(date)
- prev_datas = []
- if prev_days > 0:
- for i in range(prev_days, 0, -1):
- prev_datas.append((date - pd.Timedelta(days=i)).strftime("%Y-%m-%d"))
- else:
- for i in range(0, -prev_days + 1):
- prev_datas.append((date + pd.Timedelta(days=i)).strftime("%Y-%m-%d"))
- return prev_datas
- def query_by_day(client: EnosAPIClient, mdm_id: str, point_ids: List[str], query_date: str) -> None:
- """
- 按天分批查询数据
- """
- json_datas = {}
- day_start = f"{query_date} 00:00:00"
- day_end = f"{query_date} 23:59:59"
- print(f"查询日期: {query_date}")
- success, items = query_historical_data(
- client, mdm_id, point_ids, day_start, day_end, "RAW"
- )
- if success:
- for item in items:
- localtime = item.get("localtime")
- if not localtime in json_datas.keys():
- json_datas[localtime] = {}
- json_datas[localtime].update(item)
- print(f"当天总计: {len(items)} 条记录")
- else:
- print(f"查询失败: {items}")
- # 保存所有数据
- if json_datas:
- df = pd.DataFrame().from_dict(json_datas.values())
- filename = f"{mdm_id}_{query_date}.parquet"
- df.to_parquet(filename, index=False)
- print(f"总共获取 {df.shape} 条记录,已保存到 {filename}")
- else:
- print(" 未获取到任何数据")
- def get_wind_farm(read_db):
- df_wind_farm = pd.read_csv('wind_farm.csv', encoding='utf-8')
- df_wind_farm_db = df_wind_farm[df_wind_farm['store_id'] == read_db]
- return df_wind_farm_db
- def get_wind_turbine():
- pass
- # ===================== 使用示例 =====================
- def main(read_db):
- # 配置参数(请替换为实际值)
- APP_KEY = "a6af2233-227e-4684-ab59-9e5f74e5718c"
- APP_SECRET = "56f24801-0429-486f-b4db-ba61a33a101c"
- API_GW_ADDRESS = "ag-cdt1.eniot.io"
- ORG_ID = "o16021383932361"
- # 设备ID列表
- MDM_IDS = ["03puSZ72"]
- # 测点ID列表(100多个测点,会自动按20个一组分批)
- POINT_IDS = "last(WCNV.DECCNVAI005),last(WCNV.DECCNVAI008),last(WCNV.DECCNVAI006),last(WCNV.DECCNVAI009),last(WCNV.DECCNVAI007),last(WCNV.DECCNVAI010),last(WTRM.TrmTmpShfBrg),first(WYAW.DECYAWDI004),last(WYAW.DECYAWAI001),first(WTUR.TurbineSts),first(WTUR.TurbineSts_Map),last(WGEN.GenSpd),last(WGEN.TemGenNonDE),last(WGEN.TemGenDriEnd),last(WROT.TemAxis1Ctrl),last(WROT.TemAxis2Ctrl),last(WROT.TemAxis3Ctrl),last(WROT.TemB1Mot),last(WROT.TemB2Mot),last(WROT.TemB3Mot),last(WROT.CurBlade1Motor),last(WROT.CurBlade2Motor),last(WROT.CurBlade3Motor),last(WROT.DECROTAI004),last(WROT.DECROTAI005),last(WROT.DECROTAI006),last(WROT.Blade1Speed),last(WROT.Blade2Speed),last(WROT.Blade3Speed),last(WCNV.DECCNVAI012),last(WCNV.DECCNVAI011),last(WTOW.TemTower),last(WTUR.DECTURAI016),last(WTUR.DECTURAI015),first(WTUR.DECTURDI030),last(WGEN.TemGenStaU),last(WGEN.TemGenStaV),last(WGEN.TemGenStaW),last(WGEN.Torque),last(WYAW.TotalTwist),first(WTUR.AIStatusCode),last(WGEN.GenReactivePW),last(WGEN.GenActivePW),last(WTUR.DECTURAI003),last(WYAW.NacellePosition),last(WNAC.TemNacelle),last(WNAC.TemOut),last(WNAC.TemNacelleCab),last(WROT.Blade1Position),last(WROT.Blade2Position),last(WROT.Blade3Position),last(WROT.Blade1Setpoint),last(WROT.Blade2Setpoint),last(WROT.Blade3Setpoint),last(WTRM.PowerStoreTRBS),last(WNAC.DECNACAI004),last(WNAC.TheoryActivePW),last(WCNV.VolConL1),last(WCNV.CurConL1),last(WCNV.VolConL2),last(WCNV.CurConL2),last(WCNV.VolConL3),last(WCNV.CurConL3),last(WGEN.TorqueSetpoint),last(WNAC.DECNACAI003),last(WCNV.GridFreq),first(WYAW.YawCCWSts),first(WTUR.CCTurbineSts),first(WYAW.YawCWSts),last(WNAC.WindDirection),first(WTUR.GenState),last(WTRM.RotorSpd),last(WNAC.WindSpeed),last(WTRM.TemGeaMSND),last(WTRM.TemGeaMSDE),last(WTRM.GBoxOilPmpP),last(WTRM.TemGeaOil),last(WTRM.GBoxSpd)".split(
- ",")
- POINT_IDS = POINT_IDS[0:15]
- # 初始化客户端
- client = EnosAPIClient(APP_KEY, APP_SECRET, API_GW_ADDRESS, ORG_ID)
- # 获取Token
- success, msg = client.get_access_token()
- if not success:
- print(f"error: {msg}")
- return
- print(f"success: {msg}")
- for query_date in get_prev_days(prev_days=1):
- with multiprocessing.Pool(6, maxtasksperchild=5) as pool:
- # 查询数据
- pool.starmap(query_by_day, [(client, mdm_id, POINT_IDS, query_date) for mdm_id in MDM_IDS])
- if __name__ == "__main__":
- read_db = 1
- args = sys.argv
- if len(args) > 1:
- read_db = int(args[1])
- print(read_db)
- main(read_db)
|