connect-to-hanchuess #5
24
.env.example
Normal file
24
.env.example
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# HanchuESS API Configuration
|
||||||
|
# Copy this file to .env and fill in your actual values
|
||||||
|
|
||||||
|
# Required: AES encryption key (must be 16, 24, or 32 bytes)
|
||||||
|
HANCHU_AES_KEY=your_aes_key_here
|
||||||
|
|
||||||
|
# Required: AES initialization vector (must be 16 bytes)
|
||||||
|
HANCHU_AES_IV=your_aes_iv_here
|
||||||
|
|
||||||
|
# Required: Login URL for the HanchuESS API
|
||||||
|
HANCHU_LOGIN_URL=https://api.example.com/login
|
||||||
|
|
||||||
|
# Optional: Login type (default: ACCOUNT)
|
||||||
|
HANCHU_LOGIN_TYPE=ACCOUNT
|
||||||
|
|
||||||
|
# Optional: HTTP timeout in seconds (default: 10)
|
||||||
|
HANCHU_HTTP_TIMEOUT=10
|
||||||
|
|
||||||
|
# Optional: Verify SSL certificates (default: true, set to false for self-signed certs)
|
||||||
|
HANCHU_VERIFY_SSL=true
|
||||||
|
|
||||||
|
# Optional: Username and password
|
||||||
|
HANCHU_USERNAME=
|
||||||
|
HANCHU_PASSWORD=
|
||||||
@@ -1,2 +1,5 @@
|
|||||||
fastapi
|
fastapi
|
||||||
uvicorn[standard]
|
uvicorn[standard]
|
||||||
|
requests>=2.31.0
|
||||||
|
pycryptodome>=3.20.0
|
||||||
|
python-dotenv>=1.0.1
|
||||||
@@ -6,6 +6,17 @@ app = FastAPI(title="HanchuESS Solar Backend API")
|
|||||||
def root():
|
def root():
|
||||||
return {"message": "Welcome to the HanchuESS Solar Backend API!"}
|
return {"message": "Welcome to the HanchuESS Solar Backend API!"}
|
||||||
|
|
||||||
|
@app.get("/get_access_token", tags=["HanchuESS"])
|
||||||
|
def get_access_token():
|
||||||
|
from service.hanchu_service import HanchuESSService
|
||||||
|
|
||||||
|
hanchu_service = HanchuESSService()
|
||||||
|
try:
|
||||||
|
access_token = hanchu_service.get_access_token()
|
||||||
|
return {"access_token": access_token}
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": str(e)}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8050)
|
uvicorn.run(app, host="0.0.0.0", port=8050)
|
||||||
80
backend/src/service/hanchu_service.py
Normal file
80
backend/src/service/hanchu_service.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
from Crypto.Util.Padding import pad
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
|
class HanchuESSService:
|
||||||
|
def __init__(self):
|
||||||
|
self.name = "HanchuESS Service"
|
||||||
|
|
||||||
|
# Load config from environment
|
||||||
|
self.aes_key = os.environ["HANCHU_AES_KEY"].encode("utf-8")
|
||||||
|
self.aes_iv = os.environ["HANCHU_AES_IV"].encode("utf-8")
|
||||||
|
self.login_url = os.environ["HANCHU_LOGIN_URL"]
|
||||||
|
self.login_type = os.getenv("HANCHU_LOGIN_TYPE", "ACCOUNT")
|
||||||
|
self.timeout = int(os.getenv("HANCHU_HTTP_TIMEOUT", "10"))
|
||||||
|
self.verify_ssl = os.getenv("HANCHU_VERIFY_SSL", "true").lower() == "true"
|
||||||
|
self.hanchu_username = os.getenv("HANCHU_USERNAME", "")
|
||||||
|
self.hanchu_password = os.getenv("HANCHU_PASSWORD", "")
|
||||||
|
|
||||||
|
# Safety checks
|
||||||
|
if len(self.aes_key) not in (16, 24, 32):
|
||||||
|
raise ValueError("AES key must be 16, 24, or 32 bytes")
|
||||||
|
if len(self.aes_iv) != 16:
|
||||||
|
raise ValueError("AES IV must be exactly 16 bytes")
|
||||||
|
|
||||||
|
def encrypt_payload(self, data: dict | str) -> str:
|
||||||
|
"""
|
||||||
|
Encrypt payload using AES-CBC and return base64 string.
|
||||||
|
"""
|
||||||
|
if not isinstance(data, str):
|
||||||
|
data = json.dumps(data, separators=(",", ":"))
|
||||||
|
|
||||||
|
cipher = AES.new(self.aes_key, AES.MODE_CBC, self.aes_iv)
|
||||||
|
ciphertext = cipher.encrypt(pad(data.encode("utf-8"), AES.block_size))
|
||||||
|
return base64.b64encode(ciphertext).decode("utf-8")
|
||||||
|
|
||||||
|
def get_access_token(self) -> str:
|
||||||
|
"""
|
||||||
|
Authenticate with Hanchu ESS and return access token.
|
||||||
|
"""
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"account": self.hanchu_username,
|
||||||
|
"password": self.hanchu_password,
|
||||||
|
"loginType": self.login_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypted_payload = self.encrypt_payload(payload)
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"User-Agent": "Mozilla/5.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
self.login_url,
|
||||||
|
json={"data": encrypted_payload},
|
||||||
|
headers=headers,
|
||||||
|
timeout=self.timeout,
|
||||||
|
verify=self.verify_ssl,
|
||||||
|
)
|
||||||
|
|
||||||
|
response.raise_for_status()
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return result["data"]["accessToken"]
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Hanchu login failed: {json.dumps(result, ensure_ascii=False)}"
|
||||||
|
)
|
||||||
@@ -5,4 +5,15 @@ services:
|
|||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
container_name: hanchuess-solar-backend
|
container_name: hanchuess-solar-backend
|
||||||
ports:
|
ports:
|
||||||
- "8050:8050"
|
- "8050:8050"
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- HANCHU_AES_KEY=${HANCHU_AES_KEY}
|
||||||
|
- HANCHU_AES_IV=${HANCHU_AES_IV}
|
||||||
|
- HANCHU_LOGIN_URL=${HANCHU_LOGIN_URL}
|
||||||
|
- HANCHU_LOGIN_TYPE=${HANCHU_LOGIN_TYPE:-ACCOUNT}
|
||||||
|
- HANCHU_HTTP_TIMEOUT=${HANCHU_HTTP_TIMEOUT:-10}
|
||||||
|
- HANCHU_VERIFY_SSL=${HANCHU_VERIFY_SSL:-true}
|
||||||
|
- HANCHU_USERNAME=${HANCHU_USERNAME:-}
|
||||||
|
- HANCHU_PASSWORD=${HANCHU_PASSWORD:-}
|
||||||
Reference in New Issue
Block a user