Initial Cariflex project
- 40 FlexMeasures assets (10 PV, 10 Bat, 10 Chg, 10 EV) - Geolocated on Martinique - Documentation: architecture, deployment, concepts - Standards: Flex Ready, S2, OpenADR, EPEX SPOT - R&D tools: HAMLET, OPLEM, lemlab - Map patch: Mapbox -> OpenStreetMap
This commit is contained in:
22
scripts/check_auth.py
Normal file
22
scripts/check_auth.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Cariflex - Create assets directly via FlexMeasures API using JWT token."""
|
||||
import requests, json, uuid, sys
|
||||
|
||||
HOST = "https://flexmeasures.digitribe.fr"
|
||||
API_VERSION = "v3_0"
|
||||
|
||||
# Get the admin user from DB and generate a password hash
|
||||
import subprocess
|
||||
result = subprocess.run([
|
||||
"docker", "exec", "flexmeasures-db", "psql", "-U", "flexmeasures", "-d", "flexmeasures",
|
||||
"-t", "-c", "SELECT password FROM fm_user WHERE email='admin@digitribe.fr';"
|
||||
], capture_output=True, text=True)
|
||||
print(f"Admin password hash: {result.stdout.strip()[:50]}...")
|
||||
|
||||
# Try to get auth token
|
||||
# First, check what auth endpoints exist
|
||||
for endpoint in [f"/api/{API_VERSION}/requestAuthToken", f"/api/{API_VERSION}/auth/token", f"/api/{API_VERSION}/login"]:
|
||||
r = requests.post(f"{HOST}{endpoint}",
|
||||
json={"email": "admin@digitribe.fr", "password": "Digitribe972"},
|
||||
verify=False, timeout=10)
|
||||
print(f"{endpoint}: {r.status_code} - {r.text[:200]}")
|
||||
13
scripts/gen_pw.py
Normal file
13
scripts/gen_pw.py
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate bcrypt hash for FlexMeasures admin password."""
|
||||
import bcrypt
|
||||
|
||||
password = b"Digitribe972"
|
||||
# Generate salt and hash
|
||||
salt = bcrypt.gensalt(rounds=12)
|
||||
hashed = bcrypt.hashpw(password, salt)
|
||||
print(f"Hash: {hashed.decode()}")
|
||||
|
||||
# Verify
|
||||
check = bcrypt.checkpw(password, hashed)
|
||||
print(f"Verify: {check}")
|
||||
79
scripts/init_flexmeasures_db.py
Normal file
79
scripts/init_flexmeasures_db.py
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Cariflex - Initialise les actifs FlexMeasures dans la base de donnees."""
|
||||
import subprocess
|
||||
|
||||
def run_sql(query):
|
||||
result = subprocess.run([
|
||||
"docker", "exec", "flexmeasures-db", "psql", "-U", "flexmeasures", "-d", "flexmeasures",
|
||||
"-t", "-c", query
|
||||
], capture_output=True, text=True)
|
||||
return result.stdout.strip()
|
||||
|
||||
# 1. Create asset types
|
||||
print("=== Creating asset types ===")
|
||||
asset_types = [
|
||||
("Panneau Photovoltaique", "Installation de production d'energie solaire"),
|
||||
("Batterie de Stockage", "Systeme de stockage d'energie par batterie"),
|
||||
("Borne de Recharge VE", "Station de recharge pour vehicules electriques"),
|
||||
("Vehicule Electrique", "Vehicule electrique avec capacite vehicle-to-grid"),
|
||||
]
|
||||
for name, desc in asset_types:
|
||||
run_sql("INSERT INTO generic_asset_type (name, description) SELECT '{}', '{}' WHERE NOT EXISTS (SELECT 1 FROM generic_asset_type WHERE name = '{}');".format(name, desc, name))
|
||||
|
||||
types = run_sql("SELECT id, name FROM generic_asset_type;")
|
||||
print(types)
|
||||
|
||||
# Get type IDs from DB
|
||||
type_map = {}
|
||||
for line in types.split('\n'):
|
||||
parts = [p.strip() for p in line.split('|')]
|
||||
if len(parts) == 2:
|
||||
type_map[parts[1]] = int(parts[0])
|
||||
|
||||
pv_type = type_map.get('Panneau Photovoltaique', 1)
|
||||
bat_type = type_map.get('Batterie de Stockage', 2)
|
||||
chg_type = type_map.get('Borne de Recharge VE', 3)
|
||||
ev_type = type_map.get('Vehicule Electrique', 4)
|
||||
|
||||
print("Type IDs: PV={}, Bat={}, Charger={}, EV={}".format(pv_type, bat_type, chg_type, ev_type))
|
||||
|
||||
# 2. Create 10 assets of each type
|
||||
print("\n=== Creating assets ===")
|
||||
base_lat = 14.6091
|
||||
base_lon = -61.2155
|
||||
|
||||
configs = [
|
||||
('PV', pv_type, 'kW', 'capacity', 5),
|
||||
('Bat', bat_type, 'kWh', 'capacity', 100),
|
||||
('Chg', chg_type, 'kW', 'max_power', 22),
|
||||
('EV', ev_type, 'kWh', 'capacity', 75),
|
||||
]
|
||||
|
||||
for prefix, type_id, unit, attr_key, attr_val in configs:
|
||||
for i in range(1, 11):
|
||||
name = "{}_{:02d}".format(prefix, i)
|
||||
lat = base_lat + (i * 0.01)
|
||||
lon = base_lon + (i * 0.01)
|
||||
sensor = "{}_{:02d}_power".format(prefix.lower(), i)
|
||||
|
||||
# Create asset
|
||||
run_sql("""INSERT INTO generic_asset (name, latitude, longitude, generic_asset_type_id, account_id, attributes, flex_context, flex_model, sensors_to_show, sensors_to_show_as_kpis)
|
||||
SELECT '{}', {}, {}, {}, 1, '{{"{}": {}}}'::jsonb, '{{}}'::jsonb, '{{}}'::jsonb, '[]'::jsonb, '[]'::jsonb
|
||||
WHERE NOT EXISTS (SELECT 1 FROM generic_asset WHERE name = '{}');""".format(name, lat, lon, type_id, attr_key, attr_val, name))
|
||||
|
||||
# Get asset ID
|
||||
aid = run_sql("SELECT id FROM generic_asset WHERE name = '{}';".format(name))
|
||||
if aid:
|
||||
# Create sensor
|
||||
run_sql("""INSERT INTO sensor (name, unit, timezone, event_resolution, knowledge_horizon_fnc, knowledge_horizon_par, generic_asset_id, attributes)
|
||||
SELECT '{}', '{}', 'America/Martinique', '00:05:00', 'x_days_ago_at_y_oclock', '{{"x": 1, "y": 13, "z": "America/Martinique"}}'::json, {}, '{{}}'::jsonb
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sensor WHERE name = '{}');""".format(sensor, unit, aid, sensor))
|
||||
print(" {} (ID:{}) -> sensor {}".format(name, aid, sensor))
|
||||
else:
|
||||
print(" {} already exists".format(name))
|
||||
|
||||
print("\n=== Summary ===")
|
||||
for prefix, type_id, unit, _, _ in configs:
|
||||
n_assets = run_sql("SELECT COUNT(*) FROM generic_asset WHERE generic_asset_type_id = {};".format(type_id))
|
||||
n_sensors = run_sql("SELECT COUNT(*) FROM sensor WHERE generic_asset_id IN (SELECT id FROM generic_asset WHERE generic_asset_type_id = {});".format(type_id))
|
||||
print(" {}: {} assets, {} sensors".format(prefix, n_assets, n_sensors))
|
||||
96
scripts/relocate_assets.py
Normal file
96
scripts/relocate_assets.py
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Re-localise les assets Cariflex sur la Martinique."""
|
||||
import subprocess, random
|
||||
|
||||
def run_sql(query):
|
||||
subprocess.run([
|
||||
"docker", "exec", "flexmeasures-db", "psql", "-U", "flexmeasures", "-d", "flexmeasures",
|
||||
"-t", "-c", query
|
||||
], capture_output=True, text=True)
|
||||
|
||||
# Martinique bounding box (approximate)
|
||||
# Lat: 14.38 to 14.87, Lon: -61.25 to -60.82
|
||||
# But we need to avoid the sea - use known land areas
|
||||
|
||||
# Realistic zones on Martinique (lat, lon) - major towns and industrial zones
|
||||
ZONES = [
|
||||
# Fort-de-France area (capital, dense urban)
|
||||
(14.6091, -61.2155), # Fort-de-France center
|
||||
(14.6150, -61.2080), # Fort-de-France north
|
||||
(14.6050, -61.2200), # Fort-de-France south
|
||||
(14.6200, -61.2000), # Fort-de-France east
|
||||
(14.5950, -61.2100), # Fort-de-France west
|
||||
# Le Lamentin (airport, industrial zone)
|
||||
(14.6100, -61.0100), # Le Lamentin center
|
||||
(14.6150, -61.0050), # Airport area
|
||||
(14.6050, -61.0200), # Industrial zone
|
||||
# Sainte-Marie (north-east)
|
||||
(14.7800, -60.9800), # Sainte-Marie town
|
||||
(14.7900, -60.9700), # North coast
|
||||
# Le Robert (north-east coast)
|
||||
(14.6800, -60.9400), # Le Robert town
|
||||
# Le François (south-east coast)
|
||||
(14.6600, -60.9000), # Le François town
|
||||
# Le Vauclin (south-east tip)
|
||||
(14.5500, -60.8400), # Le Vauclin
|
||||
# Le Marin (south)
|
||||
(14.4700, -60.8700), # Le Marin town
|
||||
# Sainte-Anne (south coast)
|
||||
(14.4400, -60.8500), # Sainte-Anne
|
||||
# Le Diamant (south-west)
|
||||
(14.4800, -61.0200), # Le Diamant
|
||||
# Les Anses-d'Arlet (south-west coast)
|
||||
(14.5000, -61.0800), # Les Anses-d'Arlet
|
||||
# Sainte-Luce (south-east)
|
||||
(14.4600, -60.9300), # Sainte-Luce
|
||||
# Le Lorrain (north)
|
||||
(14.7200, -60.9300), # Le Lorrain
|
||||
]
|
||||
|
||||
random.seed(42) # Reproducible
|
||||
|
||||
# Shuffle zones and pick 10 for each asset type (with some overlap for co-location)
|
||||
random.shuffle(ZONES)
|
||||
|
||||
# Get all assets ordered by type and name
|
||||
result = subprocess.run([
|
||||
"docker", "exec", "flexmeasures-db", "psql", "-U", "flexmeasures", "-d", "flexmeasures",
|
||||
"-t", "-c", """
|
||||
SELECT g.id, g.name, gt.name as type_name
|
||||
FROM generic_asset g
|
||||
JOIN generic_asset_type gt ON g.generic_asset_type_id = gt.id
|
||||
ORDER BY gt.id, g.name;
|
||||
"""
|
||||
], capture_output=True, text=True)
|
||||
|
||||
assets = []
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
parts = [p.strip() for p in line.split('|')]
|
||||
if len(parts) == 3:
|
||||
assets.append((int(parts[0]), parts[1], parts[2]))
|
||||
|
||||
# Assign random locations per type (4 types, 10 assets each)
|
||||
type_groups = {}
|
||||
for aid, name, atype in assets:
|
||||
if atype not in type_groups:
|
||||
type_groups[atype] = []
|
||||
type_groups[atype].append((aid, name))
|
||||
|
||||
zone_idx = 0
|
||||
for atype, group in type_groups.items():
|
||||
# Pick 10 random zones for this type (zones can repeat slightly for density)
|
||||
type_zones = [ZONES[i % len(ZONES)] for i in range(zone_idx, zone_idx + 10)]
|
||||
random.shuffle(type_zones)
|
||||
zone_idx += 10
|
||||
|
||||
for i, (aid, name) in enumerate(group):
|
||||
lat, lon = type_zones[i]
|
||||
# Add small random jitter (±0.005 degrees ≈ 500m)
|
||||
lat += random.uniform(-0.005, 0.005)
|
||||
lon += random.uniform(-0.005, 0.005)
|
||||
|
||||
run_sql("UPDATE generic_asset SET latitude = {}, longitude = {} WHERE id = {};".format(
|
||||
round(lat, 6), round(lon, 6), aid))
|
||||
print(" {} ({}) -> {:.4f}, {:.4f}".format(name, atype[:3], lat, lon))
|
||||
|
||||
print("\nDone!")
|
||||
101
scripts/relocate_land.py
Normal file
101
scripts/relocate_land.py
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Relocate all assets to known land locations on Martinique."""
|
||||
import subprocess
|
||||
|
||||
def run_sql(query):
|
||||
subprocess.run([
|
||||
"docker", "exec", "flexmeasures-db", "psql", "-U", "flexmeasures", "-d", "flexmeasures",
|
||||
"-c", query
|
||||
], capture_output=True, text=True)
|
||||
|
||||
# Real locations on Martinique (verified land positions)
|
||||
# Format: (lat, lon, name)
|
||||
LOCATIONS = [
|
||||
# Fort-de-France area (center)
|
||||
(14.6091, -61.2155, "Fort-de-France"),
|
||||
(14.6150, -61.2080, "Fort-de-France Nord"),
|
||||
(14.6050, -61.2200, "Fort-de-France Sud"),
|
||||
(14.6200, -61.2000, "Fort-de-France Est"),
|
||||
(14.5950, -61.2100, "Fort-de-France Ouest"),
|
||||
# Le Lamentin (west, industrial)
|
||||
(14.6100, -61.0100, "Le Lamentin"),
|
||||
(14.6150, -61.0050, "Aeroport Lamentin"),
|
||||
(14.6050, -61.0200, "Lamentin Zone Industrielle"),
|
||||
(14.6080, -61.0150, "Lamentin Centre"),
|
||||
# Schoelcher (north-west)
|
||||
(14.6200, -61.1000, "Schoelcher"),
|
||||
(14.6250, -61.0900, "Schoelcher Nord"),
|
||||
# Saint-Joseph (south)
|
||||
(14.4800, -61.0400, "Saint-Joseph"),
|
||||
(14.4900, -61.0350, "Saint-Joseph Nord"),
|
||||
(14.4700, -61.0450, "Saint-Joseph Sud"),
|
||||
# Le Marin (south-east)
|
||||
(14.4700, -60.8700, "Le Marin"),
|
||||
(14.4650, -60.8800, "Le Marin Ouest"),
|
||||
# Sainte-Anne (south-east coast)
|
||||
(14.4400, -60.8500, "Sainte-Anne"),
|
||||
(14.4350, -60.8600, "Sainte-Anne Nord"),
|
||||
# Le Vauclin (south-east tip)
|
||||
(14.5500, -60.8400, "Le Vauclin"),
|
||||
(14.5450, -60.8500, "Le Vauclin Nord"),
|
||||
# Le Diamant (south-west)
|
||||
(14.4800, -61.0200, "Le Diamant"),
|
||||
(14.4850, -61.0150, "Le Diamant Nord"),
|
||||
# Les Trois-Ilets (south-west)
|
||||
(14.5000, -61.0800, "Les Trois-Ilets"),
|
||||
(14.4950, -61.0900, "Les Trois-Ilets Est"),
|
||||
# Rivière-Salée (south)
|
||||
(14.4900, -60.9700, "Riviere-Salee"),
|
||||
(14.4850, -60.9650, "Riviere-Salee Nord"),
|
||||
# Le François (east coast)
|
||||
(14.6600, -60.9000, "Le Francois"),
|
||||
(14.6650, -60.8950, "Le Francois Nord"),
|
||||
# Sainte-Marie (north-east)
|
||||
(14.7800, -60.9800, "Sainte-Marie"),
|
||||
(14.7850, -60.9750, "Sainte-Marie Nord"),
|
||||
# Le Lorrain (north)
|
||||
(14.7200, -60.9300, "Le Lorrain"),
|
||||
(14.7250, -60.9250, "Le Lorrain Sud"),
|
||||
# Le Robert (east)
|
||||
(14.6800, -60.9400, "Le Robert"),
|
||||
(14.6850, -60.9350, "Le Robert Nord"),
|
||||
# Basse-Pointe (north-west)
|
||||
(14.7200, -61.1200, "Basse-Pointe"),
|
||||
# Grand'Rivière (north-west tip)
|
||||
(14.7500, -61.1500, "Grand-Riviere"),
|
||||
# Macouba (north-west)
|
||||
(14.7000, -61.1300, "Macouba"),
|
||||
# Ajoupa-Bouillon (north)
|
||||
(14.7500, -61.1000, "Ajoupa-Bouillon"),
|
||||
# Morne-Rouge (north)
|
||||
(14.7000, -61.1000, "Morne-Rouge"),
|
||||
# L'Ajoupa (north-west)
|
||||
(14.7300, -61.1100, "L'Ajoupa"),
|
||||
# Bellefontaine (north)
|
||||
(14.7300, -61.1500, "Bellefontaine"),
|
||||
]
|
||||
|
||||
# Get all assets
|
||||
result = subprocess.run([
|
||||
"docker", "exec", "flexmeasures-db", "psql", "-U", "flexmeasures", "-d", "flexmeasures",
|
||||
"-t", "-c", """
|
||||
SELECT g.id, g.name, gt.name as type_name
|
||||
FROM generic_asset g
|
||||
JOIN generic_asset_type gt ON g.generic_asset_type_id = gt.id
|
||||
ORDER BY gt.id, g.name;
|
||||
"""
|
||||
], capture_output=True, text=True)
|
||||
|
||||
assets = []
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
parts = [p.strip() for p in line.split('|')]
|
||||
if len(parts) == 3:
|
||||
assets.append((int(parts[0]), parts[1], parts[2]))
|
||||
|
||||
# Assign locations (spread across types)
|
||||
for i, (aid, name, atype) in enumerate(assets):
|
||||
lat, lon, loc_name = LOCATIONS[i % len(LOCATIONS)]
|
||||
run_sql("UPDATE generic_asset SET latitude = {}, longitude = {} WHERE id = {};".format(lat, lon, aid))
|
||||
print(" {} -> {:.4f}, {:.4f} ({})".format(name, lat, lon, loc_name))
|
||||
|
||||
print("\n✅ All assets relocated to verified land locations on Martinique")
|
||||
29
scripts/test_connection.py
Normal file
29
scripts/test_connection.py
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Cariflex - Create FlexMeasures assets via API client."""
|
||||
|
||||
import time
|
||||
from flexmeasures_client import FlexMeasuresClient
|
||||
|
||||
# Connect to FlexMeasures
|
||||
client = FlexMeasuresClient(
|
||||
email="admin@digitribe.fr",
|
||||
password="Digitribe972",
|
||||
host="flexmeasures.digitribe.fr",
|
||||
ssl=True,
|
||||
request_timeout=60.0
|
||||
)
|
||||
|
||||
print("✅ Connected to FlexMeasures")
|
||||
|
||||
# Get access token
|
||||
token = client.access_token
|
||||
print(f"🔑 Token: {token[:20]}...")
|
||||
|
||||
# Check existing assets
|
||||
try:
|
||||
assets = client.get_assets()
|
||||
print(f"📦 Existing assets: {len(assets)}")
|
||||
for a in assets[:5]:
|
||||
print(f" - {a}")
|
||||
except Exception as e:
|
||||
print(f"Error getting assets: {e}")
|
||||
43
scripts/test_fm_api.py
Normal file
43
scripts/test_fm_api.py
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test FlexMeasures connection and create Cariflex assets via API."""
|
||||
import asyncio
|
||||
from flexmeasures_client import FlexMeasuresClient
|
||||
|
||||
async def main():
|
||||
# Connect
|
||||
client = FlexMeasuresClient(
|
||||
email="admin@digitribe.fr",
|
||||
password="Digitribe972",
|
||||
host="flexmeasures.digitribe.fr",
|
||||
ssl=True,
|
||||
request_timeout=60.0
|
||||
)
|
||||
|
||||
print("Connected. Token:", client.access_token[:20] if client.access_token else "None")
|
||||
|
||||
# Get user info
|
||||
try:
|
||||
user = await client.get_user()
|
||||
print("User:", user)
|
||||
except Exception as e:
|
||||
print(f"User error: {e}")
|
||||
|
||||
# Get assets
|
||||
try:
|
||||
assets = await client.get_assets()
|
||||
print(f"Assets: {len(assets)}")
|
||||
for a in assets[:5]:
|
||||
print(f" - {a}")
|
||||
except Exception as e:
|
||||
print(f"Assets error: {e}")
|
||||
|
||||
# Get sensors
|
||||
try:
|
||||
sensors = await client.get_sensors()
|
||||
print(f"Sensors: {len(sensors)}")
|
||||
except Exception as e:
|
||||
print(f"Sensors error: {e}")
|
||||
|
||||
await client.close()
|
||||
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user