Bonjour à tous,
J’utilise le très bon plugin de @Noyax37 : plugin-apsystemssunspec.
Cependant, le Modbus ne remonte pas les données des T.O.R. de mesure, donc pas possible d’avoir :
- production,
- export,
- import,
- consommation.
Pour compléter, je récupère ces infos directement depuis le site EMA APSystems, via l’API fournie :
Apsystems OpenAPI User Manual (PDF)
Remarques importantes
API gratuite = limitée à 1000 appels/mois (~30/jour) → impossible de faire du temps réel.
Il vous faut un compte EMA APSystems actif
apsystemsema
Données récupérées
Scenario 1 - A lancer tous les jours à 23h59
- Energie produite(kWh)
- Energie consommée (kWh)
- Energie importée (kWh)
- Energie exportée (kWh)
Par :
- par heure
- par jour
- par mois
- par an
Précision : on récupère toujours tout l’historique de la période en cours (par ex. toutes les journées du mois actuel).
Scenario 2 - A lancer toutes les 20min de 8h à 20h afin de ne pas exploser le nb de jetons de l’API
- Puissance produite (W)
- Puissance consommée (W)
- Puissance importée-exporté (W)
Fréquence des données : 5min
Cron à utiliser : */20 8-20 * * *
Mise en place
Récupérer vos identifiants API
Dans l’interface EMA :
appId→ Settings > OpenAPI ServiceappSecret→ Settings > OpenAPI Servicesid→ Settings > Account Detail > Personal Info > OTHER > SIDeid→ Settings > Account Detail > ECU Info > ECU ID
Créer un équipement (plugin Virtual)
Notez son ID : $equipementId (les commandes se créeront toutes seules).
(ca peut- etre le meme équipement pour les 2 scenarios
Créer un scénario Jeedom (à lancer chaque jour à 23:59)
Copiez-collez les blocs code ci-dessous.
Attention, scripts PHP intégralement réalisé avec chatGPT
Scénarios
👉 Scenario 1 : Données d'energie 1X/jour à 23h59 - Cliquez pour déplier le scénario
$debugMode = 0;
$equipementId = 1162;
$sid = 'XXXX';
$eid = 'XXXX';
$appId = 'XXXX';
$appSecret = 'XXXX';
$baseUrl = 'https://api.apsystemsema.com:9282';
$eqLogic = eqLogic::byId($equipementId);
if (!is_object($eqLogic)) {
$scenario->setLog('Équipement non trouvé');
return;
}
function checkOrCreateCmd($logicalId, $name, $eqLogic, $scenario, $debugMode) {
$cmd = cmd::byEqLogicIdAndLogicalId($eqLogic->getId(), $logicalId);
if (!is_object($cmd)) {
$cmdSameName = cmd::byEqLogicIdCmdName($eqLogic->getId(), $name);
if (is_object($cmdSameName)) $name .= ' (auto)';
$cmd = new cmd();
$cmd->setLogicalId($logicalId);
$cmd->setEqLogic_id($eqLogic->getId());
$cmd->setType('info');
$cmd->setSubType('numeric');
$cmd->setIsHistorized(1);
$cmd->setUnite('kWh');
$cmd->setName($name);
$cmd->save();
if ($debugMode) $scenario->setLog("Commande créée : $name ($logicalId)");
}
return $cmd;
}
function safeEvent($cmd, $value, $timestamp, &$counter, &$summary, $scenario, $debugMode) {
if (is_null($value)) {
if ($debugMode) $scenario->setLog("Ignoré null pour {$cmd->getName()} @$timestamp");
return;
}
$newValue = round((float)$value, 3);
$datetime = date('Y-m-d H:i:s', $timestamp);
$existing = history::byCmdIdAtDatetime($cmd->getId(), $datetime);
if (is_object($existing) && round((float)$existing->getValue(), 3) === $newValue) {
if ($debugMode) $scenario->setLog("Déjà en base, pas de mise à jour : {$cmd->getName()} @$datetime");
return;
}
if ($debugMode) $scenario->setLog("🔧 {$cmd->getName()} → $newValue @$datetime");
$cmd->event($newValue, $datetime);
$summary[] = "{$cmd->getName()} = $newValue @$datetime";
$counter++;
}
function apiRequest($path, $appId, $appSecret, $baseUrl, $scenario, $debugMode) {
$urlSegments = explode('/', strtok($path, '?'));
$lastSegment = end($urlSegments);
$timestamp = round(microtime(true) * 1000);
$nonce = strtolower(bin2hex(random_bytes(16)));
$stringToSign = implode('/', [$timestamp, $nonce, $appId, $lastSegment, 'GET', 'HmacSHA256']);
$signature = base64_encode(hash_hmac('sha256', $stringToSign, $appSecret, true));
$headers = ['X-CA-AppId: ' . $appId, 'X-CA-Timestamp: ' . $timestamp, 'X-CA-Nonce: ' . $nonce, 'X-CA-Signature-Method: HmacSHA256', 'X-CA-Signature: ' . $signature];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . $path);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
$scenario->setLog("$path (HTTP $httpCode)");
return null;
}
if ($debugMode) $scenario->setLog("$path → $response");
return json_decode($response, true);
}
$cmds = [];
foreach ([
'produced_hourly' => 'Energie produite horaire', 'consumed_hourly' => 'Energie consommé horaire', 'exported_hourly' => 'Energie exporté horaire', 'imported_hourly' => 'Energie immporté horaire',
'produced_daily' => 'Energie produite jour', 'consumed_daily' => 'Energie consommé jour', 'exported_daily' => 'Energie exporté jour', 'imported_daily' => 'Energie immporté jour',
'produced_monthly' => 'Energie produite mois', 'consumed_monthly' => 'Energie consommé mois', 'exported_monthly' => 'Energie exporté mois', 'imported_monthly' => 'Energie immporté mois',
'produced_yearly' => 'Energie produite année', 'consumed_yearly' => 'Energie consommé année', 'exported_yearly' => 'Energie exporté année', 'imported_yearly' => 'Energie importé année'
] as $id => $name) {
$cmds[$id] = checkOrCreateCmd($id, $name, $eqLogic, $scenario, $debugMode);
}
$modifCount = 0;
$summaryLog = [];
foreach (['hourly' => 'Y-m-d', 'daily' => 'Y-m', 'monthly' => 'Y', 'yearly' => ''] as $level => $format) {
$dateParam = $format ? date($format) : '';
$url = "/user/api/v2/systems/$sid/devices/meter/period/$eid?energy_level=$level" . ($dateParam ? "&date_range=$dateParam" : '');
$data = apiRequest($url, $appId, $appSecret, $baseUrl, $scenario, $debugMode);
if ($data && isset($data['data'])) {
$times = $data['data']['time'] ?? range(0, count($data['data']['produced']) -1);
foreach ($times as $i => $time) {
$produced = $data['data']['produced'][$i];
$consumed = $data['data']['consumed'][$i];
$imported = $data['data']['imported'][$i];
$exported = $data['data']['exported'][$i];
if ($level === 'hourly') $timestamp = strtotime(date('Y-m-d') . " $time:00:00");
elseif ($level === 'daily') $timestamp = strtotime(date('Y-m') . '-' . str_pad($time, 2, '0', STR_PAD_LEFT) . ' 00:00:00');
elseif ($level === 'monthly') $timestamp = strtotime(date('Y') . '-' . str_pad($time, 2, '0', STR_PAD_LEFT) . '-01 00:00:00');
else $timestamp = strtotime($time . '-01-01 00:00:00');
safeEvent($cmds['produced_' . $level], $produced, $timestamp, $modifCount, $summaryLog, $scenario, $debugMode);
safeEvent($cmds['consumed_' . $level], $consumed, $timestamp, $modifCount, $summaryLog, $scenario, $debugMode);
safeEvent($cmds['imported_' . $level], $imported, $timestamp, $modifCount, $summaryLog, $scenario, $debugMode);
safeEvent($cmds['exported_' . $level], $exported, $timestamp, $modifCount, $summaryLog, $scenario, $debugMode);
}
}
}
$scenario->setLog("Total valeurs mises à jour : $modifCount");
if ($debugMode && count($summaryLog)) {
$scenario->setLog("📝 Résumé des mises à jour :");
foreach ($summaryLog as $line) {
$scenario->setLog(" → $line");
}
}
👉 Scenario 2 : Données de puissance 1X/20min de 8h à 20h - Cliquez pour déplier le scénario complet
// Script Jeedom pour récupérer et historiser les puissances minutely sans doublon + résumé final
$debugMode = 0; // Mettre 1 pour debug complet, 0 pour réduire logs
$equipementId = 1162;
$sid = 'XXXXX';
$eid = 'XXXXX';
$appId = 'XXXXX';
$appSecret = 'XXXXX';
$baseUrl = 'https://api.apsystemsema.com:9282';
$eqLogic = eqLogic::byId($equipementId);
if (!is_object($eqLogic)) {
$scenario->setLog('Equipement non trouvé');
return;
}
function apiRequest($path, $appId, $appSecret, $baseUrl, $debugMode, $scenario) {
$urlSegments = explode('/', strtok($path, '?'));
$lastSegment = end($urlSegments);
$timestamp = round(microtime(true) * 1000);
$nonce = strtolower(bin2hex(random_bytes(16)));
$stringToSign = implode('/', [$timestamp, $nonce, $appId, $lastSegment, 'GET', 'HmacSHA256']);
$signature = base64_encode(hash_hmac('sha256', $stringToSign, $appSecret, true));
$headers = ['X-CA-AppId: ' . $appId, 'X-CA-Timestamp: ' . $timestamp, 'X-CA-Nonce: ' . $nonce, 'X-CA-Signature-Method: HmacSHA256', 'X-CA-Signature: ' . $signature];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . $path);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
$scenario->setLog("[apiRequest] Erreur $httpCode : $response");
return null;
}
if ($debugMode) $scenario->setLog("[apiRequest] Réponse $path → $response");
return json_decode($response, true);
}
function checkOrCreateCmd($logicalId, $name, $eqLogic) {
$cmd = cmd::byEqLogicIdAndLogicalId($eqLogic->getId(), $logicalId);
if (!is_object($cmd)) {
$cmd = new cmd();
$cmd->setLogicalId($logicalId);
$cmd->setEqLogic_id($eqLogic->getId());
$cmd->setType('info');
$cmd->setSubType('numeric');
$cmd->setName($name);
$cmd->setIsHistorized(1);
$cmd->setUnite('W');
$cmd->save();
}
return $cmd;
}
function saveIfDifferent($cmd, $value, $timestamp, &$count, $scenario) {
$datetime = date('Y-m-d H:i:s', $timestamp);
$existing = history::byCmdIdAtDatetime($cmd->getId(), $datetime);
$lastValue = is_object($existing) ? floatval($existing->getValue()) : null;
$newValue = round((float)$value, 2);
if ($lastValue !== $newValue) {
$scenario->setLog("[SAVE] {$cmd->getName()} $datetime → $newValue W");
$cmd->event($newValue, $datetime);
$count++;
}
}
$today = date('Y-m-d');
$path = "/user/api/v2/systems/$sid/devices/meter/period/$eid?energy_level=minutely&date_range=$today";
$data = apiRequest($path, $appId, $appSecret, $baseUrl, $debugMode, $scenario);
$updates = 0;
if ($data && isset($data['data']['time'])) {
$times = $data['data']['time'];
$powers = $data['data']['power'];
$cmdProduced = checkOrCreateCmd('produced_minutely', 'Puissance produite', $eqLogic);
$cmdConsumed = checkOrCreateCmd('consumed_minutely', 'Puissance consommee', $eqLogic);
$cmdImported = checkOrCreateCmd('imported_exported_minutely', 'Puissance importee-exportee', $eqLogic);
foreach ($times as $i => $timeStr) {
$timestamp = strtotime("$today $timeStr:00");
if (isset($powers['produced'][$i])) saveIfDifferent($cmdProduced, $powers['produced'][$i], $timestamp, $updates, $scenario);
if (isset($powers['consumed'][$i])) saveIfDifferent($cmdConsumed, $powers['consumed'][$i], $timestamp, $updates, $scenario);
if (isset($powers['imported_exported'][$i])) saveIfDifferent($cmdImported, $powers['imported_exported'][$i], $timestamp, $updates, $scenario);
}
$scenario->setLog("Résumé : $updates mises à jour effectuées");
} else {
$scenario->setLog('[ERREUR] Aucune donnée minutely trouvée ou format inattendu.');
}
Les commandes sont ensuite disponibles pour en faire ce que vous voulez …
SI vous modifiez le script, n’hesitez pas à partager
Norbert



