Pörssiohjain on Springin päälle rakennettu verkkopalvelu, joka hakee Nord Poolista pörssisähkön hinnat ja ohjaa laitteita niiden perusteella.
Ohjauksien avulla käyttäjä voi:
- ajastaa laitteita käymään halvimpien tuntien aikana halutun ajan
- estää laitteiden käynnin, kun sähkön hinta ylittää asetetun rajan
- tehdä myös manuaalisia ohjauksia
Tehorajoitukset tuovat järjestelmään tiedon talon reaaliaikaisesta kulutuksesta. Jos kulutus nousee liian korkeaksi, järjestelmä voi kytkeä ohjattuja laitteita pois päältä. Tavoitteena on pienentää tai välttyä tehomaksuilta.
Laitteet (esim. Shelly) kutsuvat control-rajapintaa ja saavat vastauksena kanavakohtaiset ohjaustilat — käytännössä tiedon siitä, mitkä konfiguroidut kanavat ovat päällä tai pois päältä.
Pörssiohjain tukee myös oman sähköntuotannon hyödyntämistä omaan kulutukseen. Jatkossa järjestelmä mahdollistaa optimoinnin oman käytön ja verkkoon myynnin välillä.
Käyttöliittymä on toteutettu Vaadinilla ja renderöidään palvelinpuolella. Sen on tarkoitus olla mahdollisimman yksinkertainen, koska sitä käytetään vain satunnaisesti.
Kaupallinen käyttö, jälleenmyynti tai palvelun tarjoaminen kolmansille osapuolille ei ole sallittua. Kehitän tästä erillistä maksullista palvelua, jossa on laajemmat ominaisuudet ja eri palvelutasot.
Jos olet hyötynyt Pörssiohjaimesta taloudellisesti niin lahjoita kahvirahaa.
- Laitteiden ohjaus pörssisähkön hinnoittelun mukaan.
- Voit ohjata halvimpien tuntien mukaan halutun aikaa päivässä.
- Voit ohjata kalliimpien tuntien mukaan jos haluat aiheuttaa markkinoille haittaa kiinteällä sopimuksella.
- Sähkön kulutuksen seuranta tehomaksuja varten.
- Ohjelma kytkee valittua kuormaa pois kun asetettu tehoraja ylittyy.
- Hälytykset sähköpostilla.
- Oman sähköntuotannon seuranta.
- Ohjaa laitteita päälle kun aurinkopaneelit tai jokin muu lähde tuottaa halutun määrän energiaa.
Aloita luomalla käyttäjätili ja kopioi tiedot talteen, sillä ne näkyvät vain tämän kerran.
Kirjautumisen jälkeen mene lisäämään laitteet ja ota UUID talteen.
UUID laitetaan esim Shelly scriptissä DEVICE_UUID kohtaan.
Luo ohjaukset My controls näkymässä asettaen moodi, vero, max hinnat.
Ohjauksissa lisätään valikosta laitteet ja kanavat mitä haluat ohjata.
Huomioi, että konfiguroinnissa kanavat alkavat arvosta 0
const DEVICE_UUID = '28217a08-df0b-4d21-b2b8-66a321cc6658';
const API_URL = 'https://porssiohjain.nitramite.com/control/' + DEVICE_UUID;
const RELAY_DEFAULT_STATE = "OFF"; // ON or OFF
const DEVICE_RELAY_COUNT = 1; // How many relay outputs your shelly has in total?
function setRelay(id, state) {
Shelly.call('Switch.Set', {id: id, on: state}, function (res, err) {
if (err) {
print('Failed to set relay', id, ':', JSON.stringify(err));
} else {
print('Relay', id, 'set to', state ? 'ON' : 'OFF');
}
});
}
function defaultStateBool() {
return RELAY_DEFAULT_STATE === "ON";
}
function applyDefaultState() {
print("Applying default relay state:", RELAY_DEFAULT_STATE);
let fallback = defaultStateBool();
for (let i = 0; i < DEVICE_RELAY_COUNT; i++) {
setRelay(i, fallback);
}
}
function getUnixTime(callback) {
Shelly.call('Sys.GetStatus', {}, function (res, err) {
if (!err && res && res.unixtime) {
callback(Math.floor(res.unixtime));
} else {
print('Failed to get time:', JSON.stringify(err));
callback(0);
}
});
}
function fetchControlData() {
Shelly.call('HTTP.REQUEST', {
method: 'GET',
url: API_URL,
headers: {'Content-Type': 'application/json'},
timeout: 10
}, function (res, err) {
if (err || res.code !== 200) {
print('API call failed:', JSON.stringify(err || res));
applyDefaultState();
return;
}
let data = {};
try {
data = JSON.parse(res.body);
} catch (e) {
print('Invalid JSON response:', res.body);
applyDefaultState();
return;
}
if (!data || Object.keys(data).length === 0) {
print('Empty JSON response');
applyDefaultState();
return;
}
for (let key in data) {
let state = !!data[key]; // convert 0/1 to boolean
setRelay(parseInt(key), state);
}
});
}
function scheduleEveryFiveMinutes() {
getUnixTime(function (now) {
let nextFiveMinute = Math.floor(now / 300) * 300 + 300;
let delay = (nextFiveMinute + 10 - now) * 1000;
print('Next API call in', delay / 1000, 'seconds');
Timer.set(delay, false, function () {
fetchControlData();
scheduleEveryFiveMinutes();
});
});
}
fetchControlData();
scheduleEveryFiveMinutes();Luo palvelussa tehoraja ja ota tehorajan UUID talteeen. Aseta se alla olevaan scriptiin, joka tulee lähettävälle laitteelle esim Shelly Pro 3EM.
const DEVICE_UUID = '28217a08-df0b-4d21-b2b8-66a321cc6658';
const API_URL = 'https://porssiohjain.nitramite.com/power/' + DEVICE_UUID;
const POLL_INTERVAL_MS = 1 * 60 * 1000; // every minute
function sendCurrentKw() {
let emStatus = Shelly.getComponentStatus("em", 0);
if (!emStatus) {
print("EM component unavailable");
return;
}
let totalW = 0;
if (typeof emStatus.total_act_power === "number") {
totalW = emStatus.total_act_power;
} else {
totalW =
(typeof emStatus.a_act_power === "number" ? emStatus.a_act_power : 0) +
(typeof emStatus.b_act_power === "number" ? emStatus.b_act_power : 0) +
(typeof emStatus.c_act_power === "number" ? emStatus.c_act_power : 0);
}
let emData = Shelly.getComponentStatus("emdata", 0);
let totalWh = 0;
if (emData) {
if (typeof emData.total_act === "number") {
totalWh = emData.total_act;
} else {
totalWh =
(typeof emData.a_total_act_energy === "number" ? emData.a_total_act_energy : 0) +
(typeof emData.b_total_act_energy === "number" ? emData.b_total_act_energy : 0) +
(typeof emData.c_total_act_energy === "number" ? emData.c_total_act_energy : 0);
}
}
let currentKw = totalW / 1000;
let totalKwh = totalWh / 1000;
let measuredAt = Date.now();
let body = JSON.stringify({
currentKw: currentKw,
totalKwh: totalKwh,
measuredAt: measuredAt
});
Shelly.call("HTTP.REQUEST", {
method: "POST",
url: API_URL,
headers: {"Content-Type": "application/json"},
body: body,
timeout: 10
}, function (res, err) {
if (err || res.code !== 200) {
print("API POST failed:", JSON.stringify(err || res));
return;
}
print("Successfully sent currentKw:", currentKw, "kW");
});
}
sendCurrentKw();
Timer.set(POLL_INTERVAL_MS, true, sendCurrentKw);export DB_HOST=localhost
export DB_PORT=5432
export DB_NAME=porssiohjain
export DB_USER=porssiohjain
export DB_PASSWORD=porssiohjain
export FINGRID_API_ENABLED=true
export FINGRID_API_KEY=xxxxxx
export RESEND_API_KEY=re_xxxxxx
export ALERTS_MAIL_ADDRESS=xxxxxx
export ADMIN_MAIL_ADDRESS=xxxxxx@xxxxxx.xx
export APP_CRYPTO_KEY=(run node app_crypto_key.js)Tämä projekti on lisensoitu GNU Affero General Public License v3.0 (AGPL-3.0) -lisenssillä. Katso lisätiedot LICENSE-tiedostosta.
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). See the LICENSE file for details.


