use of org.openremote.model.asset.Asset in project openremote by openremote.
the class KNXProtocol method startAssetImport.
@Override
public Future<Void> startAssetImport(byte[] fileData, Consumer<AssetTreeNode[]> assetConsumer) {
return executorService.submit(() -> {
ZipInputStream zin = null;
try {
boolean fileFound = false;
zin = new ZipInputStream(new ByteArrayInputStream(fileData));
ZipEntry zipEntry = zin.getNextEntry();
while (zipEntry != null) {
if (zipEntry.getName().endsWith("/0.xml")) {
fileFound = true;
break;
}
zipEntry = zin.getNextEntry();
}
if (!fileFound) {
String msg = "Failed to find '0.xml' in project file";
LOG.info(msg);
throw new IllegalStateException(msg);
}
// Create a transform factory instance.
TransformerFactory tfactory = new net.sf.saxon.TransformerFactoryImpl();
// Create a transformer for the stylesheet.
InputStream inputStream = KNXProtocol.class.getResourceAsStream("/org/openremote/agent/protocol/knx/ets_calimero_group_name.xsl");
String xsd = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
// Get weird behaviour sometimes without this
xsd = xsd.trim().replaceFirst("^([\\W]+)<", "<");
LOG.warning(xsd);
Transformer transformer = tfactory.newTransformer(new StreamSource(new StringReader(xsd)));
// Set the URIResolver
transformer.setURIResolver(new ETSFileURIResolver(fileData));
// Transform the source XML
String xml = IOUtils.toString(zin, StandardCharsets.UTF_8);
// Get weird behaviour sometimes without this
xml = xml.trim().replaceFirst("^([\\W]+)<", "<");
LOG.warning(xml);
StringWriter writer = new StringWriter();
StringReader reader = new StringReader(xml);
transformer.transform(new StreamSource(reader), new StreamResult(writer));
xml = writer.toString();
// we use a map of state-based data points and read from the transformed xml
final DatapointMap<StateDP> datapoints = new DatapointMap<>();
try (final XmlReader r = XmlInputFactory.newInstance().createXMLStreamReader(new StringReader(xml))) {
datapoints.load(r);
} catch (final KNXMLException e) {
String msg = "Error loading parsed ETS file: " + e.getMessage();
LOG.warning(msg);
throw new IllegalStateException(msg, e);
}
Map<String, Asset<?>> createdAssets = new HashMap<>();
for (StateDP dp : datapoints.getDatapoints()) {
if (dp.getName().endsWith("#A")) {
createAsset(dp, false, createdAssets);
} else if (dp.getName().endsWith("#S")) {
createAsset(dp, true, createdAssets);
} else if (dp.getName().endsWith("#SA") || dp.getName().endsWith("#AS")) {
createAsset(dp, false, createdAssets);
createAsset(dp, true, createdAssets);
} else {
LOG.info("Only group addresses ending on #A, #S, #AS or #SA will be imported. Ignoring: " + dp.getName());
}
}
assetConsumer.accept(createdAssets.values().stream().map(AssetTreeNode::new).toArray(AssetTreeNode[]::new));
} catch (Exception e) {
LOG.log(Level.WARNING, "ETS import error", e);
} finally {
if (zin != null) {
try {
zin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}, null);
}
use of org.openremote.model.asset.Asset in project openremote by openremote.
the class AssetDatapointService method purgeDataPoints.
protected void purgeDataPoints() {
LOG.info("Starting data points purge daily task");
try {
// Get list of attributes that have custom durations
List<Asset<?>> assets = assetStorageService.findAll(new AssetQuery().attributes(new AttributePredicate().meta(new NameValuePredicate(MetaItemType.DATA_POINTS_MAX_AGE_DAYS, null))));
List<Pair<String, Attribute<?>>> attributes = assets.stream().map(asset -> asset.getAttributes().stream().filter(assetAttribute -> assetAttribute.hasMeta(MetaItemType.DATA_POINTS_MAX_AGE_DAYS)).map(assetAttribute -> new Pair<String, Attribute<?>>(asset.getId(), assetAttribute)).collect(toList())).flatMap(List::stream).collect(toList());
// Purge data points not in the above list using default duration
LOG.fine("Purging data points of attributes that use default max age days of " + maxDatapointAgeDays);
persistenceService.doTransaction(em -> em.createQuery("delete from AssetDatapoint dp " + "where dp.timestamp < :dt" + buildWhereClause(attributes, true)).setParameter("dt", Date.from(timerService.getNow().truncatedTo(DAYS).minus(maxDatapointAgeDays, DAYS))).executeUpdate());
if (!attributes.isEmpty()) {
// Purge data points that have specific age constraints
Map<Integer, List<Pair<String, Attribute<?>>>> ageAttributeRefMap = attributes.stream().collect(groupingBy(attributeRef -> attributeRef.value.getMetaValue(MetaItemType.DATA_POINTS_MAX_AGE_DAYS).orElse(maxDatapointAgeDays)));
ageAttributeRefMap.forEach((age, attrs) -> {
LOG.fine("Purging data points of " + attrs.size() + " attributes that use a max age of " + age);
try {
persistenceService.doTransaction(em -> em.createQuery("delete from AssetDatapoint dp " + "where dp.timestamp < :dt" + buildWhereClause(attrs, false)).setParameter("dt", Date.from(timerService.getNow().truncatedTo(DAYS).minus(age, DAYS))).executeUpdate());
} catch (Exception e) {
LOG.log(Level.SEVERE, "An error occurred whilst deleting data points, this should not happen", e);
}
});
}
} catch (Exception e) {
LOG.log(Level.WARNING, "Failed to run data points purge", e);
}
LOG.info("Finished data points purge daily task");
}
use of org.openremote.model.asset.Asset in project openremote by openremote.
the class EnergyOptimisationService method runOptimisation.
/**
* Runs the optimisation routine for the specified time; it is important that this method does not throw an
* exception as it will cancel the scheduled task thus stopping future optimisations.
*/
protected void runOptimisation(String optimisationAssetId, Instant optimisationTime) {
OptimisationInstance optimisationInstance = assetOptimisationInstanceMap.get(optimisationAssetId);
if (optimisationInstance == null) {
return;
}
LOG.finer(getLogPrefix(optimisationAssetId) + "Running for time '" + formatter.format(optimisationTime));
EnergyOptimiser optimiser = optimisationInstance.energyOptimiser;
int intervalCount = optimiser.get24HourIntervalCount();
double intervalSize = optimiser.getIntervalSize();
LOG.finest(getLogPrefix(optimisationAssetId) + "Fetching child assets of type '" + ElectricitySupplierAsset.class.getSimpleName() + "'");
List<ElectricitySupplierAsset> supplierAssets = assetStorageService.findAll(new AssetQuery().types(ElectricitySupplierAsset.class).recursive(true).parents(optimisationAssetId)).stream().filter(asset -> asset.hasAttribute(ElectricitySupplierAsset.TARIFF_IMPORT)).map(asset -> (ElectricitySupplierAsset) asset).collect(Collectors.toList());
if (supplierAssets.size() != 1) {
LOG.warning(getLogPrefix(optimisationAssetId) + "Expected exactly one " + ElectricitySupplierAsset.class.getSimpleName() + " asset with a '" + ElectricitySupplierAsset.TARIFF_IMPORT.getName() + "' attribute but found: " + supplierAssets.size());
return;
}
double[] powerNets = new double[intervalCount];
ElectricitySupplierAsset supplierAsset = supplierAssets.get(0);
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest(getLogPrefix(optimisationAssetId) + "Found child asset of type '" + ElectricitySupplierAsset.class.getSimpleName() + "': " + supplierAsset);
}
// Do some basic validation
if (supplierAsset.getTariffImport().isPresent()) {
LOG.warning(getLogPrefix(optimisationAssetId) + ElectricitySupplierAsset.class.getSimpleName() + " asset '" + ElectricitySupplierAsset.TARIFF_IMPORT.getName() + "' attribute has no value");
}
LOG.finest(getLogPrefix(optimisationAssetId) + "Fetching optimisable child assets of type '" + ElectricityStorageAsset.class.getSimpleName() + "'");
List<ElectricityStorageAsset> optimisableStorageAssets = assetStorageService.findAll(new AssetQuery().recursive(true).parents(optimisationAssetId).types(ElectricityStorageAsset.class).attributes(new LogicGroup<>(LogicGroup.Operator.AND, Collections.singletonList(new LogicGroup<>(LogicGroup.Operator.OR, new AttributePredicate(ElectricityStorageAsset.SUPPORTS_IMPORT.getName(), new BooleanPredicate(true)), new AttributePredicate(ElectricityStorageAsset.SUPPORTS_EXPORT.getName(), new BooleanPredicate(true)))), new AttributePredicate().name(new StringPredicate(ElectricityAsset.POWER_SETPOINT.getName()))))).stream().map(asset -> (ElectricityStorageAsset) asset).collect(Collectors.toList());
List<ElectricityStorageAsset> finalOptimisableStorageAssets = optimisableStorageAssets;
optimisableStorageAssets = optimisableStorageAssets.stream().filter(asset -> {
// Exclude force charged assets (so we don't mess with the setpoint)
if (forceChargeAssetIds.contains(asset.getId())) {
LOG.finest("Optimisable asset was requested to force charge so it won't be optimised: " + asset.getId());
@SuppressWarnings("OptionalGetWithoutIsPresent") Attribute<Double> powerAttribute = asset.getAttribute(ElectricityAsset.POWER).get();
double[] powerLevels = get24HAttributeValues(asset.getId(), powerAttribute, optimiser.getIntervalSize(), intervalCount, optimisationTime);
IntStream.range(0, intervalCount).forEach(i -> powerNets[i] += powerLevels[i]);
double currentEnergyLevel = asset.getEnergyLevel().orElse(0d);
double maxEnergyLevel = getElectricityStorageAssetEnergyLevelMax(asset);
if (currentEnergyLevel >= maxEnergyLevel) {
LOG.info("Force charged asset has reached maxEnergyLevelPercentage so stopping charging: " + asset.getId());
forceChargeAssetIds.remove(asset.getId());
assetProcessingService.sendAttributeEvent(new AttributeEvent(asset.getId(), ElectricityStorageAsset.POWER_SETPOINT, 0d));
assetProcessingService.sendAttributeEvent(new AttributeEvent(asset.getId(), ElectricityStorageAsset.FORCE_CHARGE, AttributeExecuteStatus.COMPLETED));
}
return false;
}
if (asset instanceof ElectricityChargerAsset) {
// Check if it has a child vehicle asset
return finalOptimisableStorageAssets.stream().noneMatch(a -> {
if (a instanceof ElectricVehicleAsset && a.getParentId().equals(asset.getId())) {
// Take the lowest power max from vehicle or charger
double vehiclePowerImportMax = a.getPowerImportMax().orElse(Double.MAX_VALUE);
double vehiclePowerExportMax = a.getPowerExportMax().orElse(Double.MAX_VALUE);
double chargerPowerImportMax = asset.getPowerImportMax().orElse(Double.MAX_VALUE);
double chargerPowerExportMax = asset.getPowerExportMax().orElse(Double.MAX_VALUE);
double smallestPowerImportMax = Math.min(vehiclePowerImportMax, chargerPowerImportMax);
double smallestPowerExportMax = Math.min(vehiclePowerExportMax, chargerPowerExportMax);
if (smallestPowerImportMax < vehiclePowerImportMax) {
LOG.fine("Reducing vehicle power import max due to connected charger limit: vehicle=" + a.getId() + ", oldPowerImportMax=" + vehiclePowerImportMax + ", newPowerImportMax=" + smallestPowerImportMax);
a.setPowerImportMax(smallestPowerImportMax);
}
if (smallestPowerExportMax < vehiclePowerExportMax) {
LOG.fine("Reducing vehicle power Export max due to connected charger limit: vehicle=" + a.getId() + ", oldPowerExportMax=" + vehiclePowerExportMax + ", newPowerExportMax=" + smallestPowerExportMax);
a.setPowerExportMax(smallestPowerExportMax);
}
LOG.finest("Excluding charger from optimisable assets and child vehicle will be used instead: " + asset.getId());
return true;
}
return false;
});
}
return true;
}).sorted(Comparator.comparingInt(asset -> asset.getEnergyLevelSchedule().map(schedule -> 0).orElse(1))).collect(Collectors.toList());
if (optimisableStorageAssets.isEmpty()) {
LOG.warning(getLogPrefix(optimisationAssetId) + "Expected at least one optimisable '" + ElectricityStorageAsset.class.getSimpleName() + " asset with a '" + ElectricityAsset.POWER_SETPOINT.getName() + "' attribute but found none");
return;
}
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest(getLogPrefix(optimisationAssetId) + "Found optimisable child assets of type '" + ElectricityStorageAsset.class.getSimpleName() + "': " + optimisableStorageAssets.stream().map(Asset::getId).collect(Collectors.joining(", ")));
}
LOG.finest(getLogPrefix(optimisationAssetId) + "Fetching plain consumer and producer child assets of type '" + ElectricityProducerAsset.class.getSimpleName() + "', '" + ElectricityConsumerAsset.class.getSimpleName() + "', '" + ElectricityStorageAsset.class.getSimpleName() + "'");
AtomicInteger count = new AtomicInteger(0);
assetStorageService.findAll(new AssetQuery().recursive(true).parents(optimisationAssetId).types(ElectricityConsumerAsset.class, ElectricityProducerAsset.class).attributes(new AttributePredicate().name(new StringPredicate(ElectricityAsset.POWER.getName())))).forEach(asset -> {
@SuppressWarnings("OptionalGetWithoutIsPresent") Attribute<Double> powerAttribute = asset.getAttribute(ElectricityAsset.POWER).get();
double[] powerLevels = get24HAttributeValues(asset.getId(), powerAttribute, optimiser.getIntervalSize(), intervalCount, optimisationTime);
IntStream.range(0, intervalCount).forEach(i -> powerNets[i] += powerLevels[i]);
count.incrementAndGet();
});
// Get power of storage assets that don't support neither import or export (treat them as plain consumers/producers)
List<ElectricityStorageAsset> plainStorageAssets = assetStorageService.findAll(new AssetQuery().recursive(true).parents(optimisationAssetId).types(ElectricityStorageAsset.class).attributes(new AttributePredicate().name(new StringPredicate(ElectricityAsset.POWER.getName())), new AttributePredicate(ElectricityStorageAsset.SUPPORTS_IMPORT.getName(), new BooleanPredicate(true), true, null), new AttributePredicate(ElectricityStorageAsset.SUPPORTS_EXPORT.getName(), new BooleanPredicate(true), true, null))).stream().map(asset -> (ElectricityStorageAsset) asset).collect(Collectors.toList());
// Exclude chargers with a power value != 0 and a child vehicle with a power value != 0 (avoid double counting - vehicle takes priority)
plainStorageAssets.stream().filter(asset -> {
if (asset instanceof ElectricityChargerAsset) {
// Check if it has a child vehicle asset also check optimisable assets as child vehicle could be in there
return plainStorageAssets.stream().noneMatch(a -> {
if (a instanceof ElectricVehicleAsset && a.getParentId().equals(asset.getId())) {
LOG.finest("Excluding charger from plain consumer/producer calculations to avoid double counting power: " + asset.getId());
return true;
}
return false;
}) && finalOptimisableStorageAssets.stream().noneMatch(a -> {
if (a instanceof ElectricVehicleAsset && a.getParentId().equals(asset.getId())) {
LOG.finest("Excluding charger from plain consumer/producer calculations to avoid double counting power: " + asset.getId());
return true;
}
return false;
});
}
return true;
}).forEach(asset -> {
@SuppressWarnings("OptionalGetWithoutIsPresent") Attribute<Double> powerAttribute = asset.getAttribute(ElectricityAsset.POWER).get();
double[] powerLevels = get24HAttributeValues(asset.getId(), powerAttribute, optimiser.getIntervalSize(), intervalCount, optimisationTime);
IntStream.range(0, intervalCount).forEach(i -> powerNets[i] += powerLevels[i]);
count.incrementAndGet();
});
if (LOG.isLoggable(Level.FINER)) {
LOG.finer(getLogPrefix(optimisationAssetId) + "Found plain consumer and producer child assets count=" + count.get());
LOG.finer("Calculated net power of consumers and producers: " + Arrays.toString(powerNets));
}
// Get supplier costs for each interval
double financialWeightingImport = optimiser.getFinancialWeighting();
double financialWeightingExport = optimiser.getFinancialWeighting();
if (financialWeightingImport < 1d && !supplierAsset.getCarbonImport().isPresent()) {
financialWeightingImport = 1d;
}
if (financialWeightingExport < 1d && !supplierAsset.getCarbonExport().isPresent()) {
financialWeightingExport = 1d;
}
double[] costsImport = get24HAttributeValues(supplierAsset.getId(), supplierAsset.getAttribute(ElectricitySupplierAsset.TARIFF_IMPORT).orElse(null), optimiser.getIntervalSize(), intervalCount, optimisationTime);
double[] costsExport = get24HAttributeValues(supplierAsset.getId(), supplierAsset.getAttribute(ElectricitySupplierAsset.TARIFF_EXPORT).orElse(null), optimiser.getIntervalSize(), intervalCount, optimisationTime);
if (financialWeightingImport < 1d || financialWeightingExport < 1d) {
double[] carbonImport = get24HAttributeValues(supplierAsset.getId(), supplierAsset.getAttribute(ElectricitySupplierAsset.CARBON_IMPORT).orElse(null), optimiser.getIntervalSize(), intervalCount, optimisationTime);
double[] carbonExport = get24HAttributeValues(supplierAsset.getId(), supplierAsset.getAttribute(ElectricitySupplierAsset.CARBON_EXPORT).orElse(null), optimiser.getIntervalSize(), intervalCount, optimisationTime);
LOG.finer(getLogPrefix(optimisationAssetId) + "Adjusting costs to include some carbon weighting, financialWeightingImport=" + financialWeightingImport + ", financialWeightingExport=" + financialWeightingExport);
for (int i = 0; i < costsImport.length; i++) {
costsImport[i] = (financialWeightingImport * costsImport[i]) + ((1 - financialWeightingImport) * carbonImport[i]);
costsExport[i] = (financialWeightingExport * costsExport[i]) + ((1 - financialWeightingExport) * carbonExport[i]);
}
}
if (LOG.isLoggable(Level.FINER)) {
LOG.finer(getLogPrefix(optimisationAssetId) + "Import costs: " + Arrays.toString(costsImport));
LOG.finer(getLogPrefix(optimisationAssetId) + "Export costs: " + Arrays.toString(costsExport));
}
// Savings variables
List<String> obsoleteUnoptimisedAssetIds = new ArrayList<>(optimisationInstance.unoptimisedStorageAssetEnergyLevels.keySet());
double unoptimisedPower = powerNets[0];
double financialCost = 0d;
double carbonCost = 0d;
double unoptimisedFinancialCost = 0d;
double unoptimisedCarbonCost = 0d;
// Optimise storage assets with priority on storage assets with an energy schedule (already sorted above)
double importPowerMax = supplierAsset.getPowerImportMax().orElse(Double.MAX_VALUE);
double exportPowerMax = -1 * supplierAsset.getPowerExportMax().orElse(Double.MAX_VALUE);
double[] importPowerMaxes = new double[intervalCount];
double[] exportPowerMaxes = new double[intervalCount];
Arrays.fill(importPowerMaxes, importPowerMax);
Arrays.fill(exportPowerMaxes, exportPowerMax);
long periodSeconds = (long) (optimiser.getIntervalSize() * 60 * 60);
for (ElectricityStorageAsset storageAsset : optimisableStorageAssets) {
boolean hasSetpoint = storageAsset.hasAttribute(ElectricityStorageAsset.POWER_SETPOINT);
boolean supportsExport = storageAsset.isSupportsExport().orElse(false);
boolean supportsImport = storageAsset.isSupportsImport().orElse(false);
LOG.finer(getLogPrefix(optimisationAssetId) + "Optimising power set points for storage asset: " + storageAsset);
if (!supportsExport && !supportsImport) {
LOG.finest(getLogPrefix(optimisationAssetId) + "Storage asset doesn't support import or export: " + storageAsset.getId());
continue;
}
if (!hasSetpoint) {
LOG.info(getLogPrefix(optimisationAssetId) + "Storage asset has no '" + ElectricityStorageAsset.POWER_SETPOINT.getName() + "' attribute so cannot be controlled: " + storageAsset.getId());
continue;
}
double energyCapacity = storageAsset.getEnergyCapacity().orElse(0d);
double energyLevel = Math.min(energyCapacity, storageAsset.getEnergyLevel().orElse(-1d));
if (energyCapacity <= 0d || energyLevel < 0) {
LOG.info(getLogPrefix(optimisationAssetId) + "Storage asset has no capacity or energy level so cannot import or export energy: " + storageAsset.getId());
continue;
}
double energyLevelMax = Math.min(energyCapacity, ((double) storageAsset.getEnergyLevelPercentageMax().orElse(100) / 100) * energyCapacity);
double energyLevelMin = Math.min(energyCapacity, ((double) storageAsset.getEnergyLevelPercentageMin().orElse(0) / 100) * energyCapacity);
double[] energyLevelMins = new double[intervalCount];
double[] energyLevelMaxs = new double[intervalCount];
Arrays.fill(energyLevelMins, energyLevelMin);
Arrays.fill(energyLevelMaxs, energyLevelMax);
// Does the storage support import and have an energy level schedule
Optional<Integer[][]> energyLevelScheduleOptional = storageAsset.getEnergyLevelSchedule();
boolean hasEnergyMinRequirement = energyLevelMin > 0 || energyLevelScheduleOptional.isPresent();
double powerExportMax = storageAsset.getPowerExportMax().map(power -> -1 * power).orElse(Double.MIN_VALUE);
double powerImportMax = storageAsset.getPowerImportMax().orElse(Double.MAX_VALUE);
int[][] energySchedule = energyLevelScheduleOptional.map(dayArr -> Arrays.stream(dayArr).map(hourArr -> Arrays.stream(hourArr).mapToInt(i -> i != null ? i : 0).toArray()).toArray(int[][]::new)).orElse(null);
if (energySchedule != null) {
LOG.finer(getLogPrefix(optimisationAssetId) + "Applying energy schedule for storage asset: " + storageAsset.getId());
optimiser.applyEnergySchedule(energyLevelMins, energyLevelMaxs, energyCapacity, energySchedule, LocalDateTime.ofInstant(Instant.ofEpochMilli(timerService.getCurrentTimeMillis()), ZoneId.systemDefault()));
}
double maxEnergyLevelMin = Arrays.stream(energyLevelMins).max().orElse(0);
boolean isConnected = storageAssetConnected(storageAsset);
// TODO: Make these a function of energy level
Function<Integer, Double> powerImportMaxCalculator = interval -> interval == 0 && !isConnected ? 0 : powerImportMax;
Function<Integer, Double> powerExportMaxCalculator = interval -> interval == 0 && !isConnected ? 0 : powerExportMax;
if (hasEnergyMinRequirement) {
LOG.finer(getLogPrefix(optimisationAssetId) + "Normalising min energy requirements for storage asset: " + storageAsset.getId());
optimiser.normaliseEnergyMinRequirements(energyLevelMins, powerImportMaxCalculator, powerExportMaxCalculator, energyLevel);
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest(getLogPrefix(optimisationAssetId) + "Min energy requirements for storage asset '" + storageAsset.getId() + "': " + Arrays.toString(energyLevelMins));
}
}
// Calculate the power setpoints for this asset and update power net values for each interval
double[] setpoints = getStoragePowerSetpoints(optimisationInstance, storageAsset, energyLevelMins, energyLevelMaxs, powerNets, importPowerMaxes, exportPowerMaxes, costsImport, costsExport);
if (setpoints != null) {
// Assume these setpoints will be applied so update the power net values with these
for (int i = 0; i < powerNets.length; i++) {
if (i == 0) {
if (!storageAssetConnected(storageAsset)) {
LOG.finer("Optimised storage asset not connected so interval 0 will not be counted or actioned: " + storageAsset.getId());
setpoints[i] = 0;
continue;
}
// Update savings/cost data with costs specific to this asset
if (setpoints[i] > 0) {
financialCost += storageAsset.getTariffImport().orElse(0d) * setpoints[i] * intervalSize;
} else {
financialCost += storageAsset.getTariffExport().orElse(0d) * -1 * setpoints[i] * intervalSize;
}
}
powerNets[i] += setpoints[i];
}
// Push the setpoints into the prediction service for the storage asset's setpoint attribute and set current setpoint
List<Pair<?, LocalDateTime>> valuesAndTimestamps = IntStream.range(1, setpoints.length).mapToObj(i -> new Pair<>(setpoints[i], LocalDateTime.ofInstant(optimisationTime.plus(periodSeconds * i, ChronoUnit.SECONDS), ZoneId.systemDefault()))).collect(Collectors.toList());
assetPredictedDatapointService.updateValues(storageAsset.getId(), ElectricityAsset.POWER_SETPOINT.getName(), valuesAndTimestamps);
}
assetProcessingService.sendAttributeEvent(new AttributeEvent(storageAsset.getId(), ElectricityAsset.POWER_SETPOINT, setpoints != null ? setpoints[0] : null));
// Update unoptimised power for this asset
obsoleteUnoptimisedAssetIds.remove(storageAsset.getId());
double assetUnoptimisedPower = getStorageUnoptimisedImportPower(optimisationInstance, optimisationAssetId, storageAsset, maxEnergyLevelMin, Math.max(0, powerImportMax - unoptimisedPower));
unoptimisedPower += assetUnoptimisedPower;
unoptimisedFinancialCost += storageAsset.getTariffImport().orElse(0d) * assetUnoptimisedPower * intervalSize;
}
// Clear out un-optimised data for not found assets
obsoleteUnoptimisedAssetIds.forEach(optimisationInstance.unoptimisedStorageAssetEnergyLevels.keySet()::remove);
// Calculate and store savings data
carbonCost = (powerNets[0] >= 0 ? supplierAsset.getCarbonImport().orElse(0d) : -1 * supplierAsset.getCarbonExport().orElse(0d)) * powerNets[0] * intervalSize;
financialCost += (powerNets[0] >= 0 ? supplierAsset.getTariffImport().orElse(0d) : -1 * supplierAsset.getTariffExport().orElse(0d)) * powerNets[0] * intervalSize;
unoptimisedCarbonCost = (unoptimisedPower >= 0 ? supplierAsset.getCarbonImport().orElse(0d) : -1 * supplierAsset.getCarbonExport().orElse(0d)) * unoptimisedPower * intervalSize;
unoptimisedFinancialCost += (unoptimisedPower >= 0 ? supplierAsset.getTariffImport().orElse(0d) : -1 * supplierAsset.getTariffExport().orElse(0d)) * unoptimisedPower * intervalSize;
double financialSaving = unoptimisedFinancialCost - financialCost;
double carbonSaving = unoptimisedCarbonCost - carbonCost;
LOG.info(getLogPrefix(optimisationAssetId) + "Current interval financial saving = " + financialSaving);
LOG.info(getLogPrefix(optimisationAssetId) + "Current interval carbon saving = " + carbonSaving);
financialSaving += optimisationInstance.optimisationAsset.getFinancialSaving().orElse(0d);
carbonSaving += optimisationInstance.optimisationAsset.getCarbonSaving().orElse(0d);
// Update in memory asset
optimisationInstance.optimisationAsset.setFinancialSaving(financialSaving);
optimisationInstance.optimisationAsset.setCarbonSaving(carbonSaving);
// Push new values into the DB
assetProcessingService.sendAttributeEvent(new AttributeEvent(optimisationAssetId, EnergyOptimisationAsset.FINANCIAL_SAVING, financialSaving));
assetProcessingService.sendAttributeEvent(new AttributeEvent(optimisationAssetId, EnergyOptimisationAsset.CARBON_SAVING, carbonSaving));
}
use of org.openremote.model.asset.Asset in project openremote by openremote.
the class NotificationService method configure.
@Override
public void configure() throws Exception {
from(NOTIFICATION_QUEUE).routeId("NotificationQueueProcessor").doTry().process(exchange -> {
Notification notification = exchange.getIn().getBody(Notification.class);
if (notification == null) {
throw new NotificationProcessingException(MISSING_NOTIFICATION, "Notification must be set");
}
LOG.finest("Processing: " + notification.getName());
if (notification.getMessage() == null) {
throw new NotificationProcessingException(MISSING_MESSAGE, "Notification message must be set");
}
Notification.Source source = exchange.getIn().getHeader(HEADER_SOURCE, () -> null, Notification.Source.class);
if (source == null) {
throw new NotificationProcessingException(MISSING_SOURCE);
}
// Validate handler and message
NotificationHandler handler = notificationHandlerMap.get(notification.getMessage().getType());
if (handler == null) {
throw new NotificationProcessingException(UNSUPPORTED_MESSAGE_TYPE, "No handler for message type: " + notification.getMessage().getType());
}
if (!handler.isValid()) {
throw new NotificationProcessingException(NOTIFICATION_HANDLER_CONFIG_ERROR, "Handler is not valid: " + handler.getTypeName());
}
if (!handler.isMessageValid(notification.getMessage())) {
throw new NotificationProcessingException(INVALID_MESSAGE);
}
// Validate access and map targets to handler compatible targets
String realm = null;
String userId = null;
String assetId = null;
AtomicReference<String> sourceId = new AtomicReference<>("");
boolean isSuperUser = false;
boolean isRestrictedUser = false;
switch(source) {
case INTERNAL:
isSuperUser = true;
break;
case CLIENT:
AuthContext authContext = exchange.getIn().getHeader(Constants.AUTH_CONTEXT, AuthContext.class);
if (authContext == null) {
// Anonymous clients cannot send notifications
throw new NotificationProcessingException(INSUFFICIENT_ACCESS);
}
realm = authContext.getAuthenticatedRealm();
userId = authContext.getUserId();
sourceId.set(userId);
isSuperUser = authContext.isSuperUser();
isRestrictedUser = identityService.getIdentityProvider().isRestrictedUser(authContext);
break;
case GLOBAL_RULESET:
isSuperUser = true;
break;
case TENANT_RULESET:
realm = exchange.getIn().getHeader(Notification.HEADER_SOURCE_ID, String.class);
sourceId.set(realm);
break;
case ASSET_RULESET:
assetId = exchange.getIn().getHeader(Notification.HEADER_SOURCE_ID, String.class);
sourceId.set(assetId);
Asset<?> asset = assetStorageService.find(assetId, false);
realm = asset.getRealm();
break;
}
LOG.info("Sending " + notification.getMessage().getType() + " notification '" + notification.getName() + "': '" + source + ":" + sourceId.get() + "' -> " + notification.getTargets());
// Check access permissions
checkAccess(source, sourceId.get(), notification.getTargets(), realm, userId, isSuperUser, isRestrictedUser, assetId);
// Get the list of notification targets
List<Notification.Target> mappedTargetsList = handler.getTargets(source, sourceId.get(), notification.getTargets(), notification.getMessage());
if (mappedTargetsList == null || mappedTargetsList.isEmpty()) {
throw new NotificationProcessingException(MISSING_TARGETS, "Notification targets must be set");
}
// Filter targets based on repeat frequency
if (!TextUtil.isNullOrEmpty(notification.getName()) && (!TextUtil.isNullOrEmpty(notification.getRepeatInterval()) || notification.getRepeatFrequency() != null)) {
mappedTargetsList = mappedTargetsList.stream().filter(target -> okToSendNotification(source, sourceId.get(), target, notification)).collect(Collectors.toList());
}
// Send message to each applicable target
AtomicBoolean success = new AtomicBoolean(true);
mappedTargetsList.forEach(target -> {
boolean targetSuccess = persistenceService.doReturningTransaction(em -> {
// commit the notification first to get the ID
SentNotification sentNotification = new SentNotification().setName(notification.getName()).setType(notification.getMessage().getType()).setSource(source).setSourceId(sourceId.get()).setTarget(target.getType()).setTargetId(target.getId()).setMessage(notification.getMessage()).setSentOn(Date.from(timerService.getNow()));
sentNotification = em.merge(sentNotification);
long id = sentNotification.getId();
try {
NotificationSendResult result = handler.sendMessage(id, source, sourceId.get(), target, notification.getMessage());
if (result.isSuccess()) {
LOG.info("Notification sent '" + id + "': " + target);
} else {
LOG.warning("Notification failed '" + id + "': " + target + ", reason=" + result.getMessage());
sentNotification.setError(TextUtil.isNullOrEmpty(result.getMessage()) ? "Unknown error" : result.getMessage());
}
// Merge the sent notification again with the message included just in case the handler modified the message
sentNotification.setMessage(notification.getMessage());
em.merge(sentNotification);
} catch (Exception e) {
LOG.log(Level.SEVERE, "Notification handler threw an exception whilst sending notification '" + id + "'", e);
sentNotification.setError(TextUtil.isNullOrEmpty(e.getMessage()) ? "Unknown error" : e.getMessage());
em.merge(sentNotification);
}
return sentNotification.getError() == null;
});
if (!targetSuccess) {
success.set(false);
}
});
exchange.getOut().setBody(success.get());
}).endDoTry().doCatch(NotificationProcessingException.class).process(handleNotificationProcessingException(LOG));
}
use of org.openremote.model.asset.Asset in project openremote by openremote.
the class PushNotificationHandler method getTargets.
@Override
public List<Notification.Target> getTargets(Notification.Source source, String sourceId, List<Notification.Target> targets, AbstractNotificationMessage message) {
// Check if message is going to a topic if so then filter consoles subscribed to that topic
PushNotificationMessage pushMessage = (PushNotificationMessage) message;
List<Notification.Target> mappedTargets = new ArrayList<>();
if (pushMessage.getTargetType() == TOPIC || pushMessage.getTargetType() == CONDITION) {
mappedTargets.add(new Notification.Target(Notification.TargetType.CUSTOM, pushMessage.getTargetType() + ": " + pushMessage.getTarget()));
return mappedTargets;
}
if (targets != null) {
targets.forEach(target -> {
Notification.TargetType targetType = target.getType();
String targetId = target.getId();
switch(targetType) {
case TENANT:
// Get all console assets with a push provider defined within the specified tenant
List<Asset<?>> consoleAssets = assetStorageService.findAll(new AssetQuery().select(new AssetQuery.Select().excludeAttributes()).tenant(new TenantPredicate(targetId)).types(ConsoleAsset.class).attributes(new AttributePredicate(ConsoleAsset.CONSOLE_PROVIDERS, null, false, new NameValuePredicate.Path(PushNotificationMessage.TYPE))));
// Get all user ids which have pushNotificationsDisabled set to false
String[] userIds = Arrays.stream(managerIdentityService.getIdentityProvider().queryUsers(new UserQuery().tenant(new TenantPredicate((targetId))))).filter(user -> Boolean.parseBoolean(user.getAttributes().getOrDefault(KEYCLOAK_USER_ATTRIBUTE_PUSH_NOTIFICATIONS_DISABLED, Collections.singletonList("false")).get(0))).map(User::getId).toArray(String[]::new);
String[] assetIds = assetStorageService.findUserAssetLinks(targetId, null, null).stream().filter(userAssetLink -> Arrays.stream(userIds).anyMatch(userId -> userId.equals(userAssetLink.getId().getUserId()))).map(userAssetLink -> userAssetLink.getId().getAssetId()).toArray(String[]::new);
// Remove consoleAssets which are linked to an User which has pushNotificationsDisabled set to false
consoleAssets = consoleAssets.stream().filter(consoleAsset -> Arrays.stream(assetIds).noneMatch(assetId -> assetId.equals(consoleAsset.getId()))).collect(Collectors.toList());
mappedTargets.addAll(consoleAssets.stream().map(asset -> new Notification.Target(Notification.TargetType.ASSET, asset.getId())).collect(Collectors.toList()));
break;
case USER:
Optional<User> user = Arrays.stream(managerIdentityService.getIdentityProvider().queryUsers(new UserQuery().ids(targetId))).findFirst();
if (user.isPresent() && !Boolean.parseBoolean(user.get().getAttributes().getOrDefault(KEYCLOAK_USER_ATTRIBUTE_PUSH_NOTIFICATIONS_DISABLED, Collections.singletonList("false")).get(0))) {
// Get all console assets linked to the specified user
String[] ids = assetStorageService.findUserAssetLinks(null, targetId, null).stream().map(userAssetLink -> userAssetLink.getId().getAssetId()).toArray(String[]::new);
if (ids.length > 0) {
mappedTargets.addAll(assetStorageService.findAll(new AssetQuery().select(new AssetQuery.Select().excludeAttributes()).ids(ids).types(ConsoleAsset.class).attributes(new AttributePredicate(ConsoleAsset.CONSOLE_PROVIDERS, null, false, new NameValuePredicate.Path(PushNotificationMessage.TYPE)))).stream().map(asset -> new Notification.Target(Notification.TargetType.ASSET, asset.getId())).collect(Collectors.toList()));
}
} else {
LOG.fine("No console assets linked to target user");
return;
}
break;
case ASSET:
// Find all console descendants of the specified asset
consoleAssets = assetStorageService.findAll(new AssetQuery().select(new AssetQuery.Select().excludeAttributes()).paths(new PathPredicate(targetId)).types(ConsoleAsset.class).attributes(new AttributePredicate(ConsoleAsset.CONSOLE_PROVIDERS, null, false, new NameValuePredicate.Path(PushNotificationMessage.TYPE))));
UserAssetLink[] userAssetLinks = consoleAssets.stream().map(consoleAsset -> assetStorageService.findUserAssetLinks(null, null, consoleAsset.getId())).flatMap(Collection::stream).toArray(UserAssetLink[]::new);
// Get all user ids which have pushNotificationsDisabled set to false
assetIds = Arrays.stream(userAssetLinks).filter(userAssetLink -> Arrays.stream(managerIdentityService.getIdentityProvider().queryUsers(new UserQuery().asset(new UserAssetPredicate(userAssetLink.getId().getAssetId())))).filter(user1 -> Boolean.parseBoolean(user1.getAttributes().getOrDefault(KEYCLOAK_USER_ATTRIBUTE_PUSH_NOTIFICATIONS_DISABLED, Collections.singletonList("false")).get(0))).map(User::getId).anyMatch(userId -> userId.equals(userAssetLink.getId().getUserId()))).map(userAssetLink -> userAssetLink.getId().getAssetId()).toArray(String[]::new);
// Remove consoleAssets which are linked to an User which has pushNotificationsDisabled set to false
consoleAssets = consoleAssets.stream().filter(consoleAsset -> Arrays.stream(assetIds).noneMatch(assetId -> assetId.equals(consoleAsset.getId()))).collect(Collectors.toList());
mappedTargets.addAll(consoleAssets.stream().map(asset -> new Notification.Target(Notification.TargetType.ASSET, asset.getId())).collect(Collectors.toList()));
break;
}
});
}
return mappedTargets;
}
Aggregations