Skip to content

Instantly share code, notes, and snippets.

@yholkamp
Forked from erdebee/frank-onbalansmarkt.js
Last active August 3, 2025 13:25
Show Gist options
  • Save yholkamp/8e6af834be1f277536e76dd355d523dd to your computer and use it in GitHub Desktop.
Save yholkamp/8e6af834be1f277536e76dd355d523dd to your computer and use it in GitHub Desktop.

Revisions

  1. yholkamp revised this gist Aug 3, 2025. 1 changed file with 0 additions and 52 deletions.
    52 changes: 0 additions & 52 deletions frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -7,37 +7,6 @@
    * Update 2025-02-10: hhi, gecombineerd resultaat voor de bijdragende individuele (Sessy) battterijen, met import/export kWhs en batterij-capaciteit
    * Update 2025-07-01: hhi, toegevoegd de mogelijkheid om de batterij resultaten te vermenigvuldigen met het aantal deelnemende batterijen
    * Update 2025-07-03: hhi, automatische bepaling van de handelsmodus en handelsstrategie van de batterij, en deze doorgeven aan Onbalansmarkt.com
    -SESSIE- <ID-EXAMPLE-OUTPUT => {
    "data": {
    "smartBatterySessions": {
    "deviceId": "---ID---",
    "fairUsePolicyVerified": false,
    "periodStartDate": "2025-02-10",
    "periodEndDate": "2025-02-10",
    "periodEpexResult": -0.3212414999999999, --> EPEX-correctie € -0,32
    "periodFrankSlim": 0.15302999999999994, --> Handelsresultaat.Frank Slim Korting € 0,15
    "periodImbalanceResult": 0.4614773533332047, --> ''''.Onbalansresultaat € 0,46
    "periodTotalResult": 0.2932658533332047, --> Totaal kortingsfactuur € 0,29
    "periodTradeIndex": null,
    "periodTradingResult": 0.6145073533332046, --> Handelsresultaat € 0,61
    "sessions": [
    {
    "cumulativeTradingResult": 0.6145073533332046,
    "date": "2025-02-10",
    "tradingResult": 0.6145073533332046,
    "result": 0.6145073533332046,
    "status": "ACTIVE",
    "tradeIndex": null
    }
    ],
    "totalTradingResult": 293.9425048467043
    }
    }
    }
    */
    const timeZone = 'Europe/Amsterdam';

    @@ -400,29 +369,8 @@ if (handelsmode === "IMBALANCE_TRADING" && handelsstrategie === "STANDARD") {
    } else {
    handelsmodus = "manual";
    }

    console.log(`Determined mode: ${handelsmodus}`);


    // Example output for the measurement to be sent to Onbalansmarkt.com
    // {
    // "timestamp": "2025-01-20T16:00:00Z",
    // "batteryResult": "8.80",
    // "batteryResultTotal": "1004.75",
    // "batteryResultEpex": "-1.40",
    // "batteryResultImbalance": "9.60",
    // "batteryResultCustom": "0.60",
    // "batteryCharge": "76",
    // "batteryPower": "-9000",
    // "chargedToday": "11",
    // "dischargedToday": "8",
    // "loadBalancingActive": "on", (on/off)
    // "solarResult": "string",
    // "chargerResult": "string",
    // "totalBatteryCycles": "143",
    // "mode": "imbalance" (imbalance/imbalance_aggressive/manual/day_ahead/self_consumption/self_consumption_plus)
    // }

    // Send the measurement to Onbalansmarkt.com
    await onbalansmarkt.sendMeasurement({
    timestamp: currentTime,
  2. yholkamp revised this gist Aug 3, 2025. 1 changed file with 244 additions and 78 deletions.
    322 changes: 244 additions & 78 deletions frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -1,10 +1,43 @@
    /**
    * JavaScript code om de opbrengst van een batterij naar Onbalansmarkt.com te sturen.
    *
    * Auteur: erdebee, met wijzigingen van verschillende gebruikers
    * Update 2025-01-24: update om impex, Frank Slim korting en totaal handelsresultaat in te sturen.
    * Update 2025-01-23: update naar netto 'totaal kortingsfactuur' ipv bruto resultaat
    *
    * Auteur: erdebee, met wijzigingen van verschillende gebruikers
    * Update 2025-02-10: hhi, gecombineerd resultaat voor de bijdragende individuele (Sessy) battterijen, met import/export kWhs en batterij-capaciteit
    * Update 2025-07-01: hhi, toegevoegd de mogelijkheid om de batterij resultaten te vermenigvuldigen met het aantal deelnemende batterijen
    * Update 2025-07-03: hhi, automatische bepaling van de handelsmodus en handelsstrategie van de batterij, en deze doorgeven aan Onbalansmarkt.com
    -SESSIE- <ID-EXAMPLE-OUTPUT => {
    "data": {
    "smartBatterySessions": {
    "deviceId": "---ID---",
    "fairUsePolicyVerified": false,
    "periodStartDate": "2025-02-10",
    "periodEndDate": "2025-02-10",
    "periodEpexResult": -0.3212414999999999, --> EPEX-correctie € -0,32
    "periodFrankSlim": 0.15302999999999994, --> Handelsresultaat.Frank Slim Korting € 0,15
    "periodImbalanceResult": 0.4614773533332047, --> ''''.Onbalansresultaat € 0,46
    "periodTotalResult": 0.2932658533332047, --> Totaal kortingsfactuur € 0,29
    "periodTradeIndex": null,
    "periodTradingResult": 0.6145073533332046, --> Handelsresultaat € 0,61
    "sessions": [
    {
    "cumulativeTradingResult": 0.6145073533332046,
    "date": "2025-02-10",
    "tradingResult": 0.6145073533332046,
    "result": 0.6145073533332046,
    "status": "ACTIVE",
    "tradeIndex": null
    }
    ],
    "totalTradingResult": 293.9425048467043
    }
    }
    }
    */
    const timeZone = 'Europe/Amsterdam';

    @@ -63,37 +96,6 @@ class FrankEnergie {
    return this.auth;
    }

    async getPrices(startDate, endDate) {
    const query = {
    query: `
    query MarketPrices($startDate: Date!, $endDate: Date!) {
    marketPricesElectricity(startDate: $startDate, endDate: $endDate) {
    from
    till
    marketPrice
    marketPriceTax
    sourcingMarkupPrice
    energyTaxPrice
    }
    marketPricesGas(startDate: $startDate, endDate: $endDate) {
    from
    till
    marketPrice
    marketPriceTax
    sourcingMarkupPrice
    energyTaxPrice
    }
    }
    `,
    variables: {
    startDate: new Date(startDate.toLocaleString('en-US', { timeZone })).toISOString().split('T')[0],
    endDate: new Date(endDate.toLocaleString('en-US', { timeZone })).toISOString().split('T')[0]
    },
    operationName: "MarketPrices"
    };

    return await this.query(query);
    }

    async getSmartBatteries() {
    if (!this.auth) {
    @@ -129,29 +131,33 @@ class FrankEnergie {

    const query = {
    query: `
    query SmartBatterySessions($startDate: String!, $endDate: String!, $deviceId: String!) {
    smartBatterySessions(
    startDate: $startDate
    endDate: $endDate
    deviceId: $deviceId
    ) {
    deviceId
    periodStartDate
    periodEndDate
    periodEpexResult
    periodFrankSlim
    periodImbalanceResult
    periodTotalResult
    periodTradeIndex
    periodTradingResult
    sessions {
    cumulativeTradingResult
    date
    tradingResult
    }
    totalTradingResult
    }
    query SmartBatterySessions($startDate: String!, $endDate: String!, $deviceId: String!) {
    smartBatterySessions(
    startDate: $startDate
    endDate: $endDate
    deviceId: $deviceId
    ) {
    deviceId
    fairUsePolicyVerified
    periodStartDate
    periodEndDate
    periodEpexResult
    periodFrankSlim
    periodImbalanceResult
    periodTotalResult
    periodTradeIndex
    periodTradingResult
    sessions {
    cumulativeTradingResult
    date
    tradingResult
    result
    status
    tradeIndex
    }
    totalTradingResult
    }
    }
    `,
    operationName: "SmartBatterySessions",
    variables: {
    @@ -164,6 +170,55 @@ class FrankEnergie {
    return await this.query(query);
    }

    async getSmartBattery(deviceId) {
    if (!this.auth) {
    throw new Error("Authentication required");
    }

    const query = {
    query: `
    query SmartBattery($deviceId: String!) {
    smartBattery(deviceId: $deviceId) {
    brand
    capacity
    createdAt
    externalReference
    id
    maxChargePower
    maxDischargePower
    provider
    updatedAt
    settings {
    aggressivenessPercentage
    algorithm
    batteryMode
    createdAt
    effectiveTill
    imbalanceTradingAggressiveness
    imbalanceTradingStrategy
    selfConsumptionTradingAllowed
    selfConsumptionTradingThresholdPrice
    tradingAlgorithm
    updatedAt
    requestedUpdate {
    aggressivenessPercentage
    batteryMode
    effectiveFrom
    imbalanceTradingStrategy
    }
    }
    }
    }
    `,
    operationName: "SmartBattery",
    variables: {
    deviceId
    }
    };

    return await this.query(query);
    }

    isAuthenticated() {
    return this.auth !== null;
    }
    @@ -188,13 +243,15 @@ class OnbalansMarkt {
    chargerResult = null,
    batteryResultEpex = null,
    batteryResultImbalance = null,
    batteryResultCustom = null
    batteryResultCustom = null,
    mode = null
    }) {
    // Validate required fields
    if (!timestamp || !batteryResult || !batteryResultTotal) {
    throw new Error('timestamp, batteryResult and batteryResultTotal are required fields');
    }


    // Prepare the payload
    const payload = {
    timestamp: timestamp.toISOString(),
    @@ -209,7 +266,8 @@ class OnbalansMarkt {
    ...(chargerResult !== null && { chargerResult: chargerResult.toString() }),
    ...(batteryResultEpex !== null && { batteryResultEpex: batteryResultEpex.toString() }),
    ...(batteryResultImbalance !== null && { batteryResultImbalance: batteryResultImbalance.toString() }),
    ...(batteryResultCustom !== null && { batteryResultCustom: batteryResultCustom.toString() })
    ...(batteryResultCustom !== null && { batteryResultCustom: batteryResultCustom.toString() }),
    ...(mode !== null && { mode: mode.toString() })
    };

    try {
    @@ -235,10 +293,36 @@ class OnbalansMarkt {
    }
    }

    class HomeyVars {
    async getVariableValue(name, defaultValue) {
    const variable = await global.get(name);
    return variable !== undefined ? variable : defaultValue;
    }

    async setVariableValue(name, value) {
    await tag(name, value);
    await global.set(name, value);
    }
    }

    const homeyVars = new HomeyVars();

    // Lees de variabelen
    const frankenergie_pw = await homeyVars.getVariableValue('frankenergie_pw', 'defaultPassword');
    const frankenergie_id = await homeyVars.getVariableValue('frankenergie_id', 'defaultEmail');
    const onbalansmarkt_apikey = await homeyVars.getVariableValue('onbalansmarkt_apikey', 'defaultApiKey');

    // Log de waarden van de variabelen
    console.log(`Frank Energie Password: ${frankenergie_pw}`);
    console.log(`Frank Energie Login: ${frankenergie_id}`);
    console.log(`Onbalansmarkt API Key: ${onbalansmarkt_apikey}`);

    const frank = new FrankEnergie();
    await frank.login("[email protected]", "mijnfrankpassword");

    const onbalansmarkt = new OnbalansMarkt("API-KEY-ONBALANSMARKT.COM");
    await frank.login(frankenergie_id, frankenergie_pw);

    const onbalansmarkt = new OnbalansMarkt(onbalansmarkt_apikey);


    // Get all smart batteries
    const batteries = await frank.getSmartBatteries();
    @@ -247,28 +331,110 @@ const batteries = await frank.getSmartBatteries();
    let currentTime = new Date();

    // wanneer je de opgenomen en geleverde kWhs beschikbaar hebt van je batterij, dan kun je die hier ophalen en aan onderstaande variabelen toewijzen.
    let kwhCharged = null;
    let kwhDischarged = null;
    // wat betreft de kwhCharged en kwhDischarged variabelen, deze is nu een gemiddelde over de gehele set van batterijen.
    // we moeten deze waarde nog vermenigvuldigen met het aantal deelnemende (en daardoor bijdragende) batterijen

    let kwhCharged = await homeyVars.getVariableValue('deltaImportPower', null);
    let kwhDischarged = await homeyVars.getVariableValue('deltaExportPower', null);
    let battCharged = await homeyVars.getVariableValue('averageBatteryLevel', null);

    // Get sessions for a specific battery
    if (batteries.data.smartBatteries.length > 0) {
    const batteryId = batteries.data.smartBatteries[0].id;
    // Accumulate results
    let accumulatedPeriodTotalResult = 0;
    let accumulatedTotalTradingResult = 0;
    let accumulatedPeriodEpexResult = 0;
    let accumulatedPeriodTradingResult = 0;
    let accumulatedPeriodFrankSlim = 0;

    // Initialize the loop counter
    let loopCounter = 0;

    // Get sessions for each battery
    for (const battery of batteries.data.smartBatteries) {
    // Increment the loop counter for each battery
    loopCounter++;

    const batteryId = battery.id;
    const sessions = await frank.getSmartBatterySessions(
    batteryId,
    currentTime,
    currentTime
    batteryId,
    currentTime,
    currentTime
    );
    await onbalansmarkt.sendMeasurement({
    timestamp: currentTime,
    batteryResult: sessions.data.smartBatterySessions.periodTotalResult,
    batteryResultTotal: sessions.data.smartBatterySessions.totalTradingResult,
    loadBalancingActive: "off", // stuur hier enkel 'on' in wanneer de batterij op dit moment beperkt is in zijn vermogen door load balancing
    chargedToday: kwhCharged !== null ? Math.round(kwhCharged) : null,
    dischargedToday: kwhDischarged !== null ? Math.round(kwhDischarged) : null,
    batteryResultEpex: sessions.data.smartBatterySessions.periodEpexResult,
    batteryResultImbalance: sessions.data.smartBatterySessions.periodTradingResult,
    batteryResultCustom: sessions.data.smartBatterySessions.periodFrankSlim
    });
    console.log("-SESSIE-", batteryId, "=>", JSON.stringify(sessions, null, 2));
    accumulatedPeriodTotalResult += sessions.data.smartBatterySessions.periodTotalResult;
    accumulatedTotalTradingResult += sessions.data.smartBatterySessions.totalTradingResult;
    accumulatedPeriodEpexResult += sessions.data.smartBatterySessions.periodEpexResult;
    accumulatedPeriodTradingResult += sessions.data.smartBatterySessions.periodTradingResult;
    accumulatedPeriodFrankSlim += sessions.data.smartBatterySessions.periodFrankSlim;
    }

    // Multiply kwhCharged and kwhDischarged by the loop counter, needed to get the accumulated charged and discharged kWhs
    if (kwhCharged !== null) {
    kwhCharged *= loopCounter;
    console.log(`kwhCharged over alle deelnemende batterijen: ${kwhCharged}`);
    }
    if (kwhDischarged !== null) {
    kwhDischarged *= loopCounter;
    console.log(`kwhDischarged over alle deelnemende batterijen: ${kwhDischarged}`);
    }

    //aanroep van getBattery, om de handelsmode ne handelstrategie van de batterij op te halen
    const battery = await frank.getSmartBattery(batteries.data.smartBatteries[0].id);
    console.log("-BATTERY- ", JSON.stringify(battery) );

    const handelsmode = battery.data.smartBattery.settings.batteryMode
    console.log(`Handelsmode: ${handelsmode}`)

    const handelsstrategie = battery.data.smartBattery.settings.imbalanceTradingStrategy
    console.log(`Handelsstrategie: ${handelsstrategie}`)

    // bepaal welke handelsmodus we gaan gebruiken, afhankelijk van de handelsmode en handelsstrategie van de batterij
    let handelsmodus = "imbalance"; //default modus is imbalance

    if (handelsmode === "IMBALANCE_TRADING" && handelsstrategie === "STANDARD") {
    handelsmodus = "imbalance";
    } else if (handelsmode === "IMBALANCE_TRADING" && handelsstrategie === "AGGRESSIVE") {
    handelsmodus = "imbalance_aggressive";
    } else if (handelsmode === "SELF_CONSUMPTION_MIX") {
    handelsmodus = "self_consumption_plus";
    } else {
    console.log("No batteries found");
    handelsmodus = "manual";
    }

    console.log(`Determined mode: ${handelsmodus}`);


    // Example output for the measurement to be sent to Onbalansmarkt.com
    // {
    // "timestamp": "2025-01-20T16:00:00Z",
    // "batteryResult": "8.80",
    // "batteryResultTotal": "1004.75",
    // "batteryResultEpex": "-1.40",
    // "batteryResultImbalance": "9.60",
    // "batteryResultCustom": "0.60",
    // "batteryCharge": "76",
    // "batteryPower": "-9000",
    // "chargedToday": "11",
    // "dischargedToday": "8",
    // "loadBalancingActive": "on", (on/off)
    // "solarResult": "string",
    // "chargerResult": "string",
    // "totalBatteryCycles": "143",
    // "mode": "imbalance" (imbalance/imbalance_aggressive/manual/day_ahead/self_consumption/self_consumption_plus)
    // }

    // Send the measurement to Onbalansmarkt.com
    await onbalansmarkt.sendMeasurement({
    timestamp: currentTime,
    batteryResult: accumulatedPeriodTotalResult,
    batteryResultTotal: accumulatedTotalTradingResult,
    batteryCharge: battCharged,
    loadBalancingActive: "off", // Stuur hier enkel 'on' in wanneer de batterij op dit moment beperkt is in zijn vermogen door load balancing
    chargedToday: kwhCharged !== null ? Math.round(kwhCharged) : null,
    dischargedToday: kwhDischarged !== null ? Math.round(kwhDischarged) : null,
    batteryResultEpex: accumulatedPeriodEpexResult,
    batteryResultImbalance: accumulatedPeriodTradingResult,
    batteryResultCustom: accumulatedPeriodFrankSlim,
    mode: handelsmodus,
    });

  3. yholkamp revised this gist Jan 30, 2025. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -17,6 +17,7 @@ class FrankEnergie {
    async query(queryData) {
    const headers = {
    'Content-Type': 'application/json',
    'User-Agent': 'Homey/FrankV1',
    ...(this.auth && { 'Authorization': `Bearer ${this.auth.authToken}` })
    };

  4. yholkamp revised this gist Jan 24, 2025. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,7 @@
    /**
    * JavaScript code om de opbrengst van een batterij naar Onbalansmarkt.com te sturen.
    *
    * Update 2025-01-24: update om impex, Frank Slim korting en totaal handelsresultaat in te sturen.
    * Update 2025-01-23: update naar netto 'totaal kortingsfactuur' ipv bruto resultaat
    *
    * Auteur: erdebee, met wijzigingen van verschillende gebruikers
  5. yholkamp revised this gist Jan 24, 2025. 1 changed file with 15 additions and 7 deletions.
    22 changes: 15 additions & 7 deletions frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    /**
    * JavaScript code om de opbrengst van een batterij naar Onbalansmarkt.com te sturen.
    *
    *
    * Update 2025-01-23: update naar netto 'totaal kortingsfactuur' ipv bruto resultaat
    *
    * Auteur: erdebee, met wijzigingen van verschillende gebruikers
    @@ -183,7 +183,10 @@ class OnbalansMarkt {
    dischargedToday = null,
    loadBalancingActive = null,
    solarResult = null,
    chargerResult = null
    chargerResult = null,
    batteryResultEpex = null,
    batteryResultImbalance = null,
    batteryResultCustom = null
    }) {
    // Validate required fields
    if (!timestamp || !batteryResult || !batteryResultTotal) {
    @@ -201,7 +204,10 @@ class OnbalansMarkt {
    ...(dischargedToday !== null && { dischargedToday: dischargedToday.toString() }),
    ...(loadBalancingActive !== null && { loadBalancingActive: loadBalancingActive.toString() }),
    ...(solarResult !== null && { solarResult: solarResult.toString() }),
    ...(chargerResult !== null && { chargerResult: chargerResult.toString() })
    ...(chargerResult !== null && { chargerResult: chargerResult.toString() }),
    ...(batteryResultEpex !== null && { batteryResultEpex: batteryResultEpex.toString() }),
    ...(batteryResultImbalance !== null && { batteryResultImbalance: batteryResultImbalance.toString() }),
    ...(batteryResultCustom !== null && { batteryResultCustom: batteryResultCustom.toString() })
    };

    try {
    @@ -238,7 +244,7 @@ const batteries = await frank.getSmartBatteries();
    // we sturen met dit script de opbrengst van de batterij, op de huidige tijd, naar Onbalansmarkt.com
    let currentTime = new Date();

    // wanneer je de opgenomen en geleverde kWhs beschikbaar hebt van je batterij, dan kun je die hier ophalen en insturen
    // wanneer je de opgenomen en geleverde kWhs beschikbaar hebt van je batterij, dan kun je die hier ophalen en aan onderstaande variabelen toewijzen.
    let kwhCharged = null;
    let kwhDischarged = null;

    @@ -250,14 +256,16 @@ if (batteries.data.smartBatteries.length > 0) {
    currentTime,
    currentTime
    );
    console.log(sessions);
    onbalansmarkt.sendMeasurement({
    await onbalansmarkt.sendMeasurement({
    timestamp: currentTime,
    batteryResult: sessions.data.smartBatterySessions.periodTotalResult,
    batteryResultTotal: sessions.data.smartBatterySessions.totalTradingResult,
    loadBalancingActive: "off", // stuur hier enkel 'on' in wanneer de batterij op dit moment beperkt is in zijn vermogen door load balancing
    chargedToday: kwhCharged !== null ? Math.round(kwhCharged) : null,
    dischargedToday: kwhDischarged !== null ? Math.round(kwhDischarged) : null
    dischargedToday: kwhDischarged !== null ? Math.round(kwhDischarged) : null,
    batteryResultEpex: sessions.data.smartBatterySessions.periodEpexResult,
    batteryResultImbalance: sessions.data.smartBatterySessions.periodTradingResult,
    batteryResultCustom: sessions.data.smartBatterySessions.periodFrankSlim
    });
    } else {
    console.log("No batteries found");
  6. yholkamp revised this gist Jan 23, 2025. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,7 @@
    /**
    * JavaScript code om de opbrengst van een batterij naar Onbalansmarkt.com te sturen.
    *
    * Update 2025-01-23: update naar netto 'totaal kortingsfactuur' ipv bruto resultaat
    *
    * Auteur: erdebee, met wijzigingen van verschillende gebruikers
    */
  7. yholkamp revised this gist Jan 23, 2025. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -132,15 +132,14 @@ class FrankEnergie {
    deviceId: $deviceId
    ) {
    deviceId
    periodStartDate
    periodEndDate
    periodEpexResult
    periodFrankSlim
    periodImbalanceResult
    periodStartDate
    periodTotalResult
    periodTradeIndex
    periodTradingResult
    periodTradingResult
    sessions {
    cumulativeTradingResult
    date
  8. yholkamp revised this gist Jan 23, 2025. 1 changed file with 7 additions and 1 deletion.
    8 changes: 7 additions & 1 deletion frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -133,7 +133,13 @@ class FrankEnergie {
    ) {
    deviceId
    periodEndDate
    periodEpexResult
    periodFrankSlim
    periodImbalanceResult
    periodStartDate
    periodTotalResult
    periodTradeIndex
    periodTradingResult
    periodTradingResult
    sessions {
    cumulativeTradingResult
    @@ -246,7 +252,7 @@ if (batteries.data.smartBatteries.length > 0) {
    console.log(sessions);
    onbalansmarkt.sendMeasurement({
    timestamp: currentTime,
    batteryResult: sessions.data.smartBatterySessions.periodTradingResult,
    batteryResult: sessions.data.smartBatterySessions.periodTotalResult,
    batteryResultTotal: sessions.data.smartBatterySessions.totalTradingResult,
    loadBalancingActive: "off", // stuur hier enkel 'on' in wanneer de batterij op dit moment beperkt is in zijn vermogen door load balancing
    chargedToday: kwhCharged !== null ? Math.round(kwhCharged) : null,
  9. yholkamp revised this gist Jan 10, 2025. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -231,7 +231,8 @@ const batteries = await frank.getSmartBatteries();
    // we sturen met dit script de opbrengst van de batterij, op de huidige tijd, naar Onbalansmarkt.com
    let currentTime = new Date();

    // wanneer je de opgenomen en geleverde kWhs beschikbaar hebt van je batterij, dan kun je die hier opha
    // wanneer je de opgenomen en geleverde kWhs beschikbaar hebt van je batterij, dan kun je die hier ophalen en insturen
    let kwhCharged = null;
    let kwhDischarged = null;

    // Get sessions for a specific battery
  10. yholkamp revised this gist Jan 5, 2025. 1 changed file with 171 additions and 162 deletions.
    333 changes: 171 additions & 162 deletions frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -1,62 +1,67 @@
    /**
    * JavaScript code om de opbrengst van een batterij naar Onbalansmarkt.com te sturen.
    *
    * Auteur: erdebee, met wijzigingen van verschillende gebruikers
    */
    const timeZone = 'Europe/Amsterdam';

    class FrankEnergie {
    constructor(authToken = null, refreshToken = null) {
    this.DATA_URL = "https://frank-graphql-prod.graphcdn.app/";
    this.auth = authToken || refreshToken ? { authToken, refreshToken } : null;
    }

    async query(queryData) {
    const headers = {
    'Content-Type': 'application/json',
    ...(this.auth && { 'Authorization': `Bearer ${this.auth.authToken}` })
    };

    try {
    const response = await fetch(this.DATA_URL, {
    method: 'POST',
    headers,
    body: JSON.stringify(queryData)
    });

    const data = await response.json();

    if (data.errors) {
    for (const error of data.errors) {
    if (error.message === "user-error:auth-not-authorised") {
    throw new Error("Authentication required");
    }
    }
    }

    return data;
    } catch (error) {
    throw new Error(`Request failed: ${error.message}`);
    constructor(authToken = null, refreshToken = null) {
    this.DATA_URL = "https://frank-graphql-prod.graphcdn.app/";
    this.auth = authToken || refreshToken ? { authToken, refreshToken } : null;
    }

    async query(queryData) {
    const headers = {
    'Content-Type': 'application/json',
    ...(this.auth && { 'Authorization': `Bearer ${this.auth.authToken}` })
    };

    try {
    const response = await fetch(this.DATA_URL, {
    method: 'POST',
    headers,
    body: JSON.stringify(queryData)
    });

    const data = await response.json();

    if (data.errors) {
    for (const error of data.errors) {
    if (error.message === "user-error:auth-not-authorised") {
    throw new Error("Authentication required");
    }
    }
    }

    return data;
    } catch (error) {
    throw new Error(`Request failed: ${error.message}`);
    }

    async login(username, password) {
    const query = {
    query: `
    }

    async login(username, password) {
    const query = {
    query: `
    mutation Login($email: String!, $password: String!) {
    login(email: $email, password: $password) {
    authToken
    refreshToken
    }
    }
    `,
    operationName: "Login",
    variables: { email: username, password }
    };

    const response = await this.query(query);
    this.auth = response.data.login;
    return this.auth;
    }

    async getPrices(startDate, endDate) {
    const query = {
    query: `
    operationName: "Login",
    variables: { email: username, password }
    };
    const response = await this.query(query);
    this.auth = response.data.login;
    return this.auth;
    }
    async getPrices(startDate, endDate) {
    const query = {
    query: `
    query MarketPrices($startDate: Date!, $endDate: Date!) {
    marketPricesElectricity(startDate: $startDate, endDate: $endDate) {
    from
    @@ -76,23 +81,23 @@ class FrankEnergie {
    }
    }
    `,
    variables: {
    startDate: new Date(startDate.toLocaleString('en-US', { timeZone })).toISOString().split('T')[0],
    endDate: new Date(endDate.toLocaleString('en-US', { timeZone })).toISOString().split('T')[0]
    },
    operationName: "MarketPrices"
    };

    return await this.query(query);
    variables: {
    startDate: new Date(startDate.toLocaleString('en-US', { timeZone })).toISOString().split('T')[0],
    endDate: new Date(endDate.toLocaleString('en-US', { timeZone })).toISOString().split('T')[0]
    },
    operationName: "MarketPrices"
    };

    return await this.query(query);
    }

    async getSmartBatteries() {
    if (!this.auth) {
    throw new Error("Authentication required");
    }

    async getSmartBatteries() {
    if (!this.auth) {
    throw new Error("Authentication required");
    }

    const query = {
    query: `

    const query = {
    query: `
    query SmartBatteries {
    smartBatteries {
    brand
    @@ -107,19 +112,19 @@ class FrankEnergie {
    }
    }
    `,
    operationName: "SmartBatteries"
    };

    return await this.query(query);
    operationName: "SmartBatteries"
    };

    return await this.query(query);
    }

    async getSmartBatterySessions(deviceId, startDate, endDate) {
    if (!this.auth) {
    throw new Error("Authentication required");
    }

    async getSmartBatterySessions(deviceId, startDate, endDate) {
    if (!this.auth) {
    throw new Error("Authentication required");
    }

    const query = {
    query: `

    const query = {
    query: `
    query SmartBatterySessions($startDate: String!, $endDate: String!, $deviceId: String!) {
    smartBatterySessions(
    startDate: $startDate
    @@ -139,80 +144,80 @@ class FrankEnergie {
    }
    }
    `,
    operationName: "SmartBatterySessions",
    variables: {
    deviceId,
    startDate: new Date(startDate.toLocaleString('en-US', { timeZone })).toISOString().split('T')[0],
    endDate: new Date(endDate.toLocaleString('en-US', { timeZone })).toISOString().split('T')[0]
    }
    };

    return await this.query(query);
    }

    isAuthenticated() {
    return this.auth !== null;
    }
    operationName: "SmartBatterySessions",
    variables: {
    deviceId,
    startDate: startDate.toLocaleDateString('en-CA', { timeZone: timeZone }), // specificeer de lokale datum in YYYY-mm-dd formaat
    endDate: endDate.toLocaleDateString('en-CA', { timeZone: timeZone })
    }
    };
    return await this.query(query);
    }
    isAuthenticated() {
    return this.auth !== null;
    }
    }

    class OnbalansMarkt {
    constructor(apiKey) {
    this.apiUrl = 'https://onbalansmarkt.com/nexus/api/live';
    this.apiKey = apiKey;
    constructor(apiKey) {
    this.apiUrl = 'https://onbalansmarkt.com/api/live';
    this.apiKey = apiKey;
    }

    async sendMeasurement({
    timestamp,
    batteryResult,
    batteryResultTotal,
    batteryCharge = null,
    batteryPower = null,
    chargedToday = null,
    dischargedToday = null,
    loadBalancingActive = null,
    solarResult = null,
    chargerResult = null
    }) {
    // Validate required fields
    if (!timestamp || !batteryResult || !batteryResultTotal) {
    throw new Error('timestamp, batteryResult and batteryResultTotal are required fields');
    }

    async sendMeasurement({
    timestamp,
    batteryResult,
    batteryResultTotal,
    batteryCharge = null,
    batteryPower = null,
    deliveryToday = null,
    productionToday = null,
    loadBalancingActive = null,
    solarResult = null,
    chargerResult = null
    }) {
    // Validate required fields
    if (!timestamp || !batteryResult || !batteryResultTotal) {
    throw new Error('timestamp, batteryResult and batteryResultTotal are required fields');
    }

    // Prepare the payload
    const payload = {
    timestamp: timestamp.toISOString(),
    batteryResult: batteryResult.toString(),
    batteryResultTotal: batteryResultTotal.toString(),
    ...(batteryCharge !== null && { batteryCharge: batteryCharge.toString() }),
    ...(batteryPower !== null && { batteryPower: batteryPower.toString() }),
    ...(deliveryToday !== null && { deliveryToday: deliveryToday.toString() }),
    ...(productionToday !== null && { productionToday: productionToday.toString() }),
    ...(loadBalancingActive !== null && { loadBalancingActive: loadBalancingActive.toString() }),
    ...(solarResult !== null && { solarResult: solarResult.toString() }),
    ...(chargerResult !== null && { chargerResult: chargerResult.toString() })
    };

    try {
    const response = await fetch(this.apiUrl, {
    method: 'POST',
    headers: {
    'Accept': 'application/json',
    'Authorization': `Bearer ${this.apiKey}`,
    'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
    });

    if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
    }

    return await response.text();
    } catch (error) {
    console.error('Error sending measurement:', error);
    throw error;
    }

    // Prepare the payload
    const payload = {
    timestamp: timestamp.toISOString(),
    batteryResult: batteryResult.toString(),
    batteryResultTotal: batteryResultTotal.toString(),
    ...(batteryCharge !== null && { batteryCharge: batteryCharge.toString() }),
    ...(batteryPower !== null && { batteryPower: batteryPower.toString() }),
    ...(chargedToday !== null && { chargedToday: chargedToday.toString() }),
    ...(dischargedToday !== null && { dischargedToday: dischargedToday.toString() }),
    ...(loadBalancingActive !== null && { loadBalancingActive: loadBalancingActive.toString() }),
    ...(solarResult !== null && { solarResult: solarResult.toString() }),
    ...(chargerResult !== null && { chargerResult: chargerResult.toString() })
    };

    try {
    const response = await fetch(this.apiUrl, {
    method: 'POST',
    headers: {
    'Accept': 'application/json',
    'Authorization': `Bearer ${this.apiKey}`,
    'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
    });

    if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
    }

    return await response.text();
    } catch (error) {
    console.error('Error sending measurement:', error);
    throw error;
    }
    }
    }

    const frank = new FrankEnergie();
    @@ -222,26 +227,30 @@ const onbalansmarkt = new OnbalansMarkt("API-KEY-ONBALANSMARKT.COM");

    // Get all smart batteries
    const batteries = await frank.getSmartBatteries();
    console.log(batteries);

    let yesterday = new Date();
    yesterday.setHours(0,0,0,0);
    yesterday.setDate(yesterday.getDate() - 1);
    // we sturen met dit script de opbrengst van de batterij, op de huidige tijd, naar Onbalansmarkt.com
    let currentTime = new Date();

    // wanneer je de opgenomen en geleverde kWhs beschikbaar hebt van je batterij, dan kun je die hier opha
    let kwhDischarged = null;

    // Get sessions for a specific battery
    if (batteries.data.smartBatteries.length > 0) {
    const batteryId = batteries.data.smartBatteries[0].id;
    const sessions = await frank.getSmartBatterySessions(
    batteryId,
    yesterday,
    yesterday
    );
    console.log(sessions);
    onbalansmarkt.sendMeasurement({
    timestamp: yesterday,
    batteryResult: sessions.data.smartBatterySessions.periodTradingResult,
    batteryResultTotal: sessions.data.smartBatterySessions.totalTradingResult,
    loadBalancingActive: "off"
    });
    const batteryId = batteries.data.smartBatteries[0].id;
    const sessions = await frank.getSmartBatterySessions(
    batteryId,
    currentTime,
    currentTime
    );
    console.log(sessions);
    onbalansmarkt.sendMeasurement({
    timestamp: currentTime,
    batteryResult: sessions.data.smartBatterySessions.periodTradingResult,
    batteryResultTotal: sessions.data.smartBatterySessions.totalTradingResult,
    loadBalancingActive: "off", // stuur hier enkel 'on' in wanneer de batterij op dit moment beperkt is in zijn vermogen door load balancing
    chargedToday: kwhCharged !== null ? Math.round(kwhCharged) : null,
    dischargedToday: kwhDischarged !== null ? Math.round(kwhDischarged) : null
    });
    } else {
    console.log("No batteries found");
    }

  11. @erdebee erdebee revised this gist Dec 10, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -207,7 +207,7 @@ class OnbalansMarkt {
    throw new Error(`HTTP error! status: ${response.status}`);
    }

    return await response.json();
    return await response.text();
    } catch (error) {
    console.error('Error sending measurement:', error);
    throw error;
  12. @erdebee erdebee revised this gist Dec 10, 2024. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -142,8 +142,8 @@ class FrankEnergie {
    operationName: "SmartBatterySessions",
    variables: {
    deviceId,
    startDate: startDate.toISOString().split('T')[0],
    endDate: endDate.toISOString().split('T')[0]
    startDate: new Date(startDate.toLocaleString('en-US', { timeZone })).toISOString().split('T')[0],
    endDate: new Date(endDate.toLocaleString('en-US', { timeZone })).toISOString().split('T')[0]
    }
    };

  13. @erdebee erdebee revised this gist Dec 10, 2024. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    const timeZone = 'Europe/Amsterdam';

    class FrankEnergie {
    constructor(authToken = null, refreshToken = null) {
    this.DATA_URL = "https://frank-graphql-prod.graphcdn.app/";
    @@ -75,8 +77,8 @@ class FrankEnergie {
    }
    `,
    variables: {
    startDate: startDate.toISOString().split('T')[0],
    endDate: endDate.toISOString().split('T')[0]
    startDate: new Date(startDate.toLocaleString('en-US', { timeZone })).toISOString().split('T')[0],
    endDate: new Date(endDate.toLocaleString('en-US', { timeZone })).toISOString().split('T')[0]
    },
    operationName: "MarketPrices"
    };
  14. @erdebee erdebee revised this gist Dec 10, 2024. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -162,7 +162,7 @@ class OnbalansMarkt {
    async sendMeasurement({
    timestamp,
    batteryResult,
    batteryResultTotal = null,
    batteryResultTotal,
    batteryCharge = null,
    batteryPower = null,
    deliveryToday = null,
    @@ -172,15 +172,15 @@ class OnbalansMarkt {
    chargerResult = null
    }) {
    // Validate required fields
    if (!timestamp || !batteryResult) {
    throw new Error('timestamp and batteryResult are required fields');
    if (!timestamp || !batteryResult || !batteryResultTotal) {
    throw new Error('timestamp, batteryResult and batteryResultTotal are required fields');
    }

    // Prepare the payload
    const payload = {
    timestamp: timestamp.toISOString(),
    batteryResult: batteryResult.toString(),
    ...(batteryResultTotal !== null && { batteryResultTotal: batteryResultTotal.toString() }),
    batteryResultTotal: batteryResultTotal.toString(),
    ...(batteryCharge !== null && { batteryCharge: batteryCharge.toString() }),
    ...(batteryPower !== null && { batteryPower: batteryPower.toString() }),
    ...(deliveryToday !== null && { deliveryToday: deliveryToday.toString() }),
    @@ -236,7 +236,7 @@ if (batteries.data.smartBatteries.length > 0) {
    );
    console.log(sessions);
    onbalansmarkt.sendMeasurement({
    timestamp: yesterday.toISOString(),
    timestamp: yesterday,
    batteryResult: sessions.data.smartBatterySessions.periodTradingResult,
    batteryResultTotal: sessions.data.smartBatterySessions.totalTradingResult,
    loadBalancingActive: "off"
  15. @erdebee erdebee revised this gist Dec 10, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -178,7 +178,7 @@ class OnbalansMarkt {

    // Prepare the payload
    const payload = {
    timestamp,
    timestamp: timestamp.toISOString(),
    batteryResult: batteryResult.toString(),
    ...(batteryResultTotal !== null && { batteryResultTotal: batteryResultTotal.toString() }),
    ...(batteryCharge !== null && { batteryCharge: batteryCharge.toString() }),
  16. @erdebee erdebee revised this gist Dec 10, 2024. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -238,6 +238,7 @@ if (batteries.data.smartBatteries.length > 0) {
    onbalansmarkt.sendMeasurement({
    timestamp: yesterday.toISOString(),
    batteryResult: sessions.data.smartBatterySessions.periodTradingResult,
    batteryResultTotal: sessions.data.smartBatterySessions.totalTradingResult,
    loadBalancingActive: "off"
    });
    }
  17. @erdebee erdebee revised this gist Dec 10, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -236,7 +236,7 @@ if (batteries.data.smartBatteries.length > 0) {
    );
    console.log(sessions);
    onbalansmarkt.sendMeasurement({
    timestamp: new Date().toISOString(),
    timestamp: yesterday.toISOString(),
    batteryResult: sessions.data.smartBatterySessions.periodTradingResult,
    loadBalancingActive: "off"
    });
  18. @erdebee erdebee created this gist Dec 8, 2024.
    244 changes: 244 additions & 0 deletions frank-onbalansmarkt.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,244 @@
    class FrankEnergie {
    constructor(authToken = null, refreshToken = null) {
    this.DATA_URL = "https://frank-graphql-prod.graphcdn.app/";
    this.auth = authToken || refreshToken ? { authToken, refreshToken } : null;
    }

    async query(queryData) {
    const headers = {
    'Content-Type': 'application/json',
    ...(this.auth && { 'Authorization': `Bearer ${this.auth.authToken}` })
    };

    try {
    const response = await fetch(this.DATA_URL, {
    method: 'POST',
    headers,
    body: JSON.stringify(queryData)
    });

    const data = await response.json();

    if (data.errors) {
    for (const error of data.errors) {
    if (error.message === "user-error:auth-not-authorised") {
    throw new Error("Authentication required");
    }
    }
    }

    return data;
    } catch (error) {
    throw new Error(`Request failed: ${error.message}`);
    }
    }

    async login(username, password) {
    const query = {
    query: `
    mutation Login($email: String!, $password: String!) {
    login(email: $email, password: $password) {
    authToken
    refreshToken
    }
    }
    `,
    operationName: "Login",
    variables: { email: username, password }
    };

    const response = await this.query(query);
    this.auth = response.data.login;
    return this.auth;
    }

    async getPrices(startDate, endDate) {
    const query = {
    query: `
    query MarketPrices($startDate: Date!, $endDate: Date!) {
    marketPricesElectricity(startDate: $startDate, endDate: $endDate) {
    from
    till
    marketPrice
    marketPriceTax
    sourcingMarkupPrice
    energyTaxPrice
    }
    marketPricesGas(startDate: $startDate, endDate: $endDate) {
    from
    till
    marketPrice
    marketPriceTax
    sourcingMarkupPrice
    energyTaxPrice
    }
    }
    `,
    variables: {
    startDate: startDate.toISOString().split('T')[0],
    endDate: endDate.toISOString().split('T')[0]
    },
    operationName: "MarketPrices"
    };

    return await this.query(query);
    }

    async getSmartBatteries() {
    if (!this.auth) {
    throw new Error("Authentication required");
    }

    const query = {
    query: `
    query SmartBatteries {
    smartBatteries {
    brand
    capacity
    createdAt
    externalReference
    id
    maxChargePower
    maxDischargePower
    provider
    updatedAt
    }
    }
    `,
    operationName: "SmartBatteries"
    };

    return await this.query(query);
    }

    async getSmartBatterySessions(deviceId, startDate, endDate) {
    if (!this.auth) {
    throw new Error("Authentication required");
    }

    const query = {
    query: `
    query SmartBatterySessions($startDate: String!, $endDate: String!, $deviceId: String!) {
    smartBatterySessions(
    startDate: $startDate
    endDate: $endDate
    deviceId: $deviceId
    ) {
    deviceId
    periodEndDate
    periodStartDate
    periodTradingResult
    sessions {
    cumulativeTradingResult
    date
    tradingResult
    }
    totalTradingResult
    }
    }
    `,
    operationName: "SmartBatterySessions",
    variables: {
    deviceId,
    startDate: startDate.toISOString().split('T')[0],
    endDate: endDate.toISOString().split('T')[0]
    }
    };

    return await this.query(query);
    }

    isAuthenticated() {
    return this.auth !== null;
    }
    }

    class OnbalansMarkt {
    constructor(apiKey) {
    this.apiUrl = 'https://onbalansmarkt.com/nexus/api/live';
    this.apiKey = apiKey;
    }

    async sendMeasurement({
    timestamp,
    batteryResult,
    batteryResultTotal = null,
    batteryCharge = null,
    batteryPower = null,
    deliveryToday = null,
    productionToday = null,
    loadBalancingActive = null,
    solarResult = null,
    chargerResult = null
    }) {
    // Validate required fields
    if (!timestamp || !batteryResult) {
    throw new Error('timestamp and batteryResult are required fields');
    }

    // Prepare the payload
    const payload = {
    timestamp,
    batteryResult: batteryResult.toString(),
    ...(batteryResultTotal !== null && { batteryResultTotal: batteryResultTotal.toString() }),
    ...(batteryCharge !== null && { batteryCharge: batteryCharge.toString() }),
    ...(batteryPower !== null && { batteryPower: batteryPower.toString() }),
    ...(deliveryToday !== null && { deliveryToday: deliveryToday.toString() }),
    ...(productionToday !== null && { productionToday: productionToday.toString() }),
    ...(loadBalancingActive !== null && { loadBalancingActive: loadBalancingActive.toString() }),
    ...(solarResult !== null && { solarResult: solarResult.toString() }),
    ...(chargerResult !== null && { chargerResult: chargerResult.toString() })
    };

    try {
    const response = await fetch(this.apiUrl, {
    method: 'POST',
    headers: {
    'Accept': 'application/json',
    'Authorization': `Bearer ${this.apiKey}`,
    'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
    });

    if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
    }

    return await response.json();
    } catch (error) {
    console.error('Error sending measurement:', error);
    throw error;
    }
    }
    }

    const frank = new FrankEnergie();
    await frank.login("[email protected]", "mijnfrankpassword");

    const onbalansmarkt = new OnbalansMarkt("API-KEY-ONBALANSMARKT.COM");

    // Get all smart batteries
    const batteries = await frank.getSmartBatteries();
    console.log(batteries);

    let yesterday = new Date();
    yesterday.setHours(0,0,0,0);
    yesterday.setDate(yesterday.getDate() - 1);

    // Get sessions for a specific battery
    if (batteries.data.smartBatteries.length > 0) {
    const batteryId = batteries.data.smartBatteries[0].id;
    const sessions = await frank.getSmartBatterySessions(
    batteryId,
    yesterday,
    yesterday
    );
    console.log(sessions);
    onbalansmarkt.sendMeasurement({
    timestamp: new Date().toISOString(),
    batteryResult: sessions.data.smartBatterySessions.periodTradingResult,
    loadBalancingActive: "off"
    });
    }