Start work on tracking the mains separately.

This commit is contained in:
Mark Milligan 2022-05-22 23:13:54 -05:00
parent d7edf3db4a
commit 0f730aac32
9 changed files with 114 additions and 68 deletions

View File

@ -35,7 +35,7 @@ public class MonitorConfig {
}
public String getHost() {
return NullUtils.isEmpty(host) ? "https://lanternpowermonitor.com/" : host;
return NullUtils.isEmpty(host) ? "https://lanternpowermonitor.com/currentmonitor/" : host;
}
public void setHost(String _host) {

View File

@ -26,6 +26,7 @@ public class Breaker implements IIdentical<Breaker> {
private BreakerPolarity polarity;
private boolean doublePower;
private BreakerType type;
private boolean main;
private transient String key;
public Breaker() {
@ -148,7 +149,7 @@ public class Breaker implements IIdentical<Breaker> {
}
public BreakerPolarity getPolarity() {
return polarity;
return polarity == null ? BreakerPolarity.NORMAL : polarity;
}
public void setPolarity(BreakerPolarity _polarity) {
@ -184,6 +185,14 @@ public class Breaker implements IIdentical<Breaker> {
type = _type;
}
public boolean isMain() {
return main;
}
public void setMain(boolean _main) {
main = _main;
}
public double getFinalCalibrationFactor() {
return getCalibrationFactor() * getSizeAmps() / 380.0;
}
@ -269,7 +278,7 @@ public class Breaker implements IIdentical<Breaker> {
@Override
public boolean isIdentical(Breaker _o) {
if (this == _o) return true;
return panel == _o.panel && space == _o.space && meter == _o.meter && hub == _o.hub && port == _o.port && sizeAmps == _o.sizeAmps && Double.compare(_o.calibrationFactor, calibrationFactor) == 0 && Double.compare(_o.lowPassFilter, lowPassFilter) == 0 && doublePower == _o.doublePower && Objects.equals(name, _o.name) && Objects.equals(description, _o.description) && polarity == _o.polarity && type == _o.type && Objects.equals(key, _o.key);
return panel == _o.panel && space == _o.space && meter == _o.meter && hub == _o.hub && port == _o.port && sizeAmps == _o.sizeAmps && Double.compare(_o.calibrationFactor, calibrationFactor) == 0 && Double.compare(_o.lowPassFilter, lowPassFilter) == 0 && doublePower == _o.doublePower && Objects.equals(name, _o.name) && Objects.equals(description, _o.description) && polarity == _o.polarity && type == _o.type;
}
@Override

View File

@ -10,6 +10,7 @@ import com.lanternsoftware.util.dao.annotations.PrimaryKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@DBSerializable(autogen = false)
public class BreakerConfig implements IIdentical<BreakerConfig> {
@ -202,10 +203,22 @@ public class BreakerConfig implements IIdentical<BreakerConfig> {
return null;
}
public boolean containsPolarity(Set<String> _groupIds, BreakerPolarity _polarity) {
for (BreakerGroup subGroup : CollectionUtils.makeNotNull(breakerGroups)) {
if (subGroup.containsPolarity(_groupIds, _polarity))
return true;
}
return false;
}
public BillingCurrency getCurrency() {
return CollectionUtils.getFirst(CollectionUtils.transformToSet(CollectionUtils.aggregate(billingPlans, BillingPlan::getRates), BillingRate::getCurrency));
}
public boolean isMainsPowerTrackedForMeter(int _meter) {
return CollectionUtils.anyQualify(getAllBreakers(), _b->_b.isMain() && (_b.getMeter() == _meter));
}
@Override
public boolean equals(Object _o) {
if (this == _o) return true;

View File

@ -182,6 +182,20 @@ public class BreakerGroup implements IIdentical<BreakerGroup> {
return groups;
}
public boolean containsPolarity(Set<String> _groupIds, BreakerPolarity _polarity) {
if ((CollectionUtils.isEmpty(_groupIds) || _groupIds.contains(id)) && CollectionUtils.anyQualify(breakers, _b->_b.getPolarity() == _polarity))
return true;
for (BreakerGroup subGroup : CollectionUtils.makeNotNull(subGroups)) {
if (subGroup.containsPolarity(_groupIds, _polarity))
return true;
}
return false;
}
public boolean isMain() {
return CollectionUtils.anyQualify(breakers, Breaker::isMain);
}
public boolean removeInvalidGroups(Set<Integer> _validPanels) {
if (subGroups != null)
subGroups.removeIf(_g->!_g.removeInvalidGroups(_validPanels));

View File

@ -8,7 +8,6 @@ import com.lanternsoftware.util.dao.annotations.DBSerializable;
import com.lanternsoftware.util.mutable.MutableDouble;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
@ -26,6 +25,7 @@ public class EnergySummary {
private EnergyViewMode viewMode;
private Date start;
private List<EnergySummary> subGroups;
private boolean main;
private float[] energy;
private float[] gridEnergy;
private double peakToGrid;
@ -40,6 +40,7 @@ public class EnergySummary {
public EnergySummary(BreakerGroup _group, List<HubPowerMinute> _power, EnergyViewMode _viewMode, Date _start, TimeZone _timezone) {
groupId = _group.getId();
groupName = _group.getName();
main = _group.isMain();
viewMode = _viewMode;
start = _start;
accountId = _group.getAccountId();
@ -50,29 +51,30 @@ public class EnergySummary {
}
public void addEnergy(BreakerGroup _group, List<HubPowerMinute> _hubPower) {
Map<String, Breaker> breakers = CollectionUtils.transformToMap(_group.getAllBreakers(), Breaker::getKey);
Map<String, BreakerGroup> breakerKeyToGroup = new HashMap<>();
Map<Integer, Breaker> breakers = CollectionUtils.transformToMap(_group.getAllBreakers(), Breaker::getIntKey);
Map<Integer, BreakerGroup> breakerKeyToGroup = new HashMap<>();
for (BreakerGroup group : _group.getAllBreakerGroups()) {
for (Breaker b : CollectionUtils.makeNotNull(group.getBreakers())) {
breakerKeyToGroup.put(b.getKey(), group);
breakerKeyToGroup.put(b.getIntKey(), group);
}
}
addEnergy(breakers, breakerKeyToGroup, _hubPower);
}
public void addEnergy(Map<String, Breaker> _breakers, Map<String, BreakerGroup> _breakerKeyToGroup, List<HubPowerMinute> _hubPower) {
public void addEnergy(Map<Integer, Breaker> _breakers, Map<Integer, BreakerGroup> _breakerKeyToGroup, List<HubPowerMinute> _hubPower) {
if (CollectionUtils.isEmpty(_hubPower) || CollectionUtils.anyQualify(_hubPower, _p -> _p.getAccountId() != accountId))
return;
_hubPower.sort(Comparator.comparing(HubPowerMinute::getMinute));
for (Date minute : CollectionUtils.transformToSet(_hubPower, HubPowerMinute::getMinuteAsDate)) {
resetEnergy(minute);
}
Set<Integer> meterMainsTracked = CollectionUtils.transformToSet(CollectionUtils.filter(_breakers.values(), Breaker::isMain), Breaker::getMeter);
int idx;
Map<Integer, Map<Integer, MeterMinute>> minutes = new HashMap<>();
for (HubPowerMinute hubPower : _hubPower) {
Date minute = hubPower.getMinuteAsDate();
for (BreakerPowerMinute breaker : CollectionUtils.makeNotNull(hubPower.getBreakers())) {
String key = breaker.breakerKey();
int key = breaker.breakerIntKey();
Breaker b = _breakers.get(key);
if (b == null)
continue;
@ -84,10 +86,12 @@ public class EnergySummary {
for (Float power : CollectionUtils.makeNotNull(breaker.getReadings())) {
if (idx >= 60)
break;
if (power > 0)
meter.usage[idx] += power;
else
meter.solar[idx] -= power;
if (!meterMainsTracked.contains(b.getMeter()) || b.isMain()) {
if (power > 0)
meter.usage[idx] += power;
else
meter.solar[idx] -= power;
}
addEnergy(group.getId(), minute, power);
idx++;
}
@ -125,7 +129,7 @@ public class EnergySummary {
for (HubPowerMinute hubPower : _hubPower) {
Date minute = hubPower.getMinuteAsDate();
for (BreakerPowerMinute breaker : CollectionUtils.makeNotNull(hubPower.getBreakers())) {
String key = breaker.breakerKey();
int key = breaker.breakerIntKey();
Breaker b = _breakers.get(key);
if (b == null)
continue;
@ -288,6 +292,14 @@ public class EnergySummary {
subGroups = _subGroups;
}
public boolean isMain() {
return main;
}
public void setMain(boolean _main) {
main = _main;
}
public float[] getEnergy() {
return energy;
}
@ -362,7 +374,7 @@ public class EnergySummary {
public double joules(Set<String> _selectedBreakers, boolean _includeSubgroups, GridFlow _mode) {
double joules = 0.0;
if (_includeSubgroups) {
if (_includeSubgroups && !isMain()) {
for (EnergySummary group : CollectionUtils.makeNotNull(subGroups)) {
joules += group.joules(_selectedBreakers, true, _mode);
}

View File

@ -15,59 +15,45 @@ import java.util.concurrent.atomic.AtomicInteger;
public class BOM {
List<LineItem> lineItems;
private static final Map<Integer, String> ctSizes = new TreeMap<>();
static {
ctSizes.put(15, "https://store.lanternpowermonitor.com/product/15a-yhdc-current-transformer/3");
ctSizes.put(20, "https://store.lanternpowermonitor.com/product/20a-yhdc-current-transformer/4");
ctSizes.put(30, "https://store.lanternpowermonitor.com/product/30a-yhdc-current-transformer/5");
ctSizes.put(50, "https://store.lanternpowermonitor.com/product/50a-yhdc-current-transformer/6");
ctSizes.put(60, "https://store.lanternpowermonitor.com/product/60a-yhdc-current-transformer/7");
ctSizes.put(100, "https://store.lanternpowermonitor.com/product/100a-yhdc-current-transformer/8");
}
public static BOM fromConfig(BreakerConfig _config) {
BOM bom = new BOM();
bom.setLineItems(new ArrayList<>());
Map<Integer, AtomicInteger> ctCnts = new TreeMap<>();
Map<Integer, AtomicInteger> ctDuplicates = new TreeMap<>();
for (Breaker breaker : CollectionUtils.makeNotNull(_config.getAllBreakers())) {
if (breaker.getSizeAmps() <= 20) {
ctCnts.computeIfAbsent(20, (_k) -> new AtomicInteger(0)).getAndIncrement();
if (breaker.getType() == BreakerType.DOUBLE_POLE_TOP_ONE_CT)
ctDuplicates.computeIfAbsent(20, (_k) -> new AtomicInteger(0)).getAndIncrement();
Map<Integer, Breaker> breakers = CollectionUtils.transformToMap(_config.getAllBreakers(), Breaker::getIntKey);
for (Breaker breaker : breakers.values()) {
if (bom.isUntrackedBottom(breakers, breaker))
continue;
for (int size : ctSizes.keySet()) {
if (breaker.getSizeAmps() <= size) {
ctCnts.computeIfAbsent(size, (_k) -> new AtomicInteger(0)).getAndIncrement();
break;
}
}
else if (breaker.getSizeAmps() <= 30) {
ctCnts.computeIfAbsent(30, (_k) -> new AtomicInteger(0)).getAndIncrement();
if (breaker.getType() == BreakerType.DOUBLE_POLE_TOP_ONE_CT)
ctDuplicates.computeIfAbsent(30, (_k) -> new AtomicInteger(0)).getAndIncrement();
}
else {
ctCnts.computeIfAbsent(50, (_k) -> new AtomicInteger(0)).getAndIncrement();
if (breaker.getType() == BreakerType.DOUBLE_POLE_TOP_ONE_CT)
ctDuplicates.computeIfAbsent(50, (_k) -> new AtomicInteger(0)).getAndIncrement();
}
}
for (Map.Entry<Integer, AtomicInteger> ctCnt : ctDuplicates.entrySet()) {
AtomicInteger cnt = ctCnts.get(ctCnt.getKey());
if (cnt != null)
cnt.getAndAdd(-ctCnt.getValue().get());
}
int breakerCnt = CollectionUtils.sumIntegers(CollectionUtils.transform(ctCnts.values(), AtomicInteger::get));
int hubCnt = (int)Math.ceil(breakerCnt/15.0);
bom.getLineItems().add(new LineItem("Lantern Power Monitor Case", "LPMC1", "https://github.com/MarkBryanMilligan/LanternPowerMonitor/tree/main/case", 0.10, 3.00, hubCnt));
bom.getLineItems().add(new LineItem("Lantern Power Monitor Case Lid", "LPMCL1", "https://github.com/MarkBryanMilligan/LanternPowerMonitor/tree/main/case", 0.10, 2.00, hubCnt));
bom.getLineItems().add(new LineItem("Lantern Power Monitor Soldering Jig", "LPMSJ1", "https://github.com/MarkBryanMilligan/LanternPowerMonitor/tree/main/case", 0.10, 4.00, 1));
bom.getLineItems().add(new LineItem("Lantern Power Monitor PCB", "LPMPCB1", "https://github.com/MarkBryanMilligan/LanternPowerMonitor/tree/main/pcb", 1.00, 5.00, hubCnt));
bom.getLineItems().add(new LineItem("Lantern Power Monitor PCB", "LPMPCB1", "https://store.lanternpowermonitor.com/product/assembled-lantern-power-monitor-pcb/1", 1.00, 5.00, hubCnt));
bom.getLineItems().add(new LineItem("Raspberry Pi 3 Model A+", "3A+", "https://www.raspberrypi.org/products/raspberry-pi-3-model-a-plus/", 25.0, 35.0, hubCnt));
bom.getLineItems().add(new LineItem("Jameco 12V AC/AC Adapter", "10428", "https://www.jameco.com/z/ACU120100Z9121-Jameco-Reliapro-AC-to-AC-Wall-Adapter-Transformer-12-Volt-AC-1000mA-Black-Straight-3-5mm-Male-Plug_10428.html", 10.95, 15.00, hubCnt));
bom.getLineItems().add(new LineItem("16gb memory card", "P-SDU16GU185GW-GE", "https://www.microcenter.com/product/486146/micro-center-16gb-microsdhc-class-10-flash-memory-card", 4.00, 5.00, hubCnt));
bom.getLineItems().add(new LineItem("40-pin GPIO header", "C169819", "https://lcsc.com/product-detail/Pin-Header-Female-Header_Ckmtw-Shenzhen-Cankemeng-C169819_C169819.html", 0.36, 0.80, hubCnt));
bom.getLineItems().add(new LineItem("MCP3008", "MCP3008-I-P", "https://www.digikey.com/en/products/detail/microchip-technology/MCP3008-I-P/319422", 2.41, 4.00, hubCnt*2));
bom.getLineItems().add(new LineItem("10uF 25V 4*5 Capacitor", "C43846", "https://lcsc.com/product-detail/Aluminum-Electrolytic-Capacitors-Leaded_CX-Dongguan-Chengxing-Elec-10uF-25V-4-5_C43846.html", 0.01, 0.10, hubCnt));
bom.getLineItems().add(new LineItem("22uF 25V 4*7 Capacitor", "C43840", "https://lcsc.com/product-detail/Aluminum-Electrolytic-Capacitors-Leaded_CX-Dongguan-Chengxing-Elec-22uF-25V-4-7_C43840.html", 0.01, 0.10, hubCnt));
bom.getLineItems().add(new LineItem("10KΩ Resistor", "C385441", "https://lcsc.com/product-detail/Metal-Film-Resistor-TH_TyoHM-RN1-2WS10K%CE%A9FT-BA1_C385441.html", 0.01, 0.10, hubCnt*2));
bom.getLineItems().add(new LineItem("12KΩ Resistor", "C385449", "https://lcsc.com/product-detail/Metal-Film-Resistor-TH_TyoHM-RN1-2WS12K%CE%A9FT-BA1_C385449.html", 0.01, 0.10, hubCnt));
bom.getLineItems().add(new LineItem("180KΩ Resistor", "C385460", "https://lcsc.com/product-detail/Metal-Film-Resistor-TH_TyoHM-RN1-2WS180K%CE%A9FT-BA1_C385460.html", 0.01, 0.10, hubCnt));
bom.getLineItems().add(new LineItem("33KΩ Resistor", "C385498", "https://lcsc.com/product-detail/Metal-Film-Resistor-TH_TyoHM-RN1-2WS33K%CE%A9FT-BA1_C385498.html", 0.01, 0.10, hubCnt));
bom.getLineItems().add(new LineItem("68KΩ Resistor", "C385541", "https://lcsc.com/product-detail/Metal-Film-Resistor-TH_TyoHM-RN1-2WS68K%CE%A9FT-BA1_C385541.html", 0.01, 0.10, hubCnt));
bom.getLineItems().add(new LineItem("3.5mm Headphone Jack", "PJ-3583-B", "https://lcsc.com/product-detail/Audio-Video-Connectors_XKB-Enterprise-PJ-3583-B_C397337.html", 0.16, 0.25, hubCnt*16));
bom.getLineItems().add(new LineItem("Jameco 12V AC/AC Adapter", "10428", "https://store.lanternpowermonitor.com/product/120vac-to-12vac-voltage-transformer/2", 10.95, 15.00, hubCnt));
bom.getLineItems().add(new LineItem("8gb Sandisk Industrial memory card", "SDSDQAF3-008G-I", "https://www.amazon.com/gp/product/B07BZ5SY18", 4.00, 5.00, hubCnt));
bom.getLineItems().add(new LineItem("M2.5x10mm Cap Screw", "A15120300ux0225", "https://www.amazon.com/gp/product/B01B1OD7IK", 0.10, 0.20, hubCnt*8));
bom.getLineItems().add(new LineItem("M2.5x11mm Female x Female Standoff", "", "https://www.ebay.com/itm/50pcs-M2-5-Female-Hex-Screw-Brass-PCB-Standoffs-Hexagonal-Spacers/172746413434", 0.15, 0.25, hubCnt*4));
bom.getLineItems().add(new LineItem("M2.5x12mm Female x Male Standoff", "", "https://www.ebay.com/itm/M2-5-2-5mm-Thread-6mm-Brass-Standoff-Spacer-Male-x-Female-20-50pcs-New/283432513974", 0.15, 0.25, hubCnt*4));
for (Map.Entry<Integer, AtomicInteger> ctCnt : ctCnts.entrySet()) {
bom.getLineItems().add(new LineItem(String.format("%d Amp Current Transformer", ctCnt.getKey()), String.format("SCT-013-0%d", ctCnt.getKey()), "N/A", 5.00, 7.00, ctCnt.getValue().get()));
bom.getLineItems().add(new LineItem(String.format("%d Amp Current Transformer", ctCnt.getKey()), String.format("SCT-013-%03d", ctCnt.getKey()), ctSizes.get(ctCnt.getKey()), 5.00, 6.00, ctCnt.getValue().get()));
}
return bom;
}
@ -93,4 +79,11 @@ public class BOM {
rows.add(CollectionUtils.asArrayList("Total", "", "", "", "", "", String.format("$%.2f", selfCost), String.format("$%.2f", shippedCost)));
return new CSV(headers, rows, headers.size());
}
private boolean isUntrackedBottom(Map<Integer, Breaker> _breakers, Breaker _breaker) {
if (_breaker.getType() != BreakerType.DOUBLE_POLE_BOTTOM)
return false;
Breaker topBreaker = _breakers.get(Breaker.intKey(_breaker.getPanel(), _breaker.getSpaceIndex() - 2));
return topBreaker != null && topBreaker.getType() == BreakerType.DOUBLE_POLE_TOP_ONE_CT;
}
}

View File

@ -40,6 +40,7 @@ public class BreakerSerializer extends AbstractDaoSerializer<Breaker>
d.put("polarity", DaoSerializer.toEnumName(_o.getPolarity()));
d.put("double_power", _o.isDoublePower());
d.put("type", DaoSerializer.toEnumName(_o.getType()));
d.put("main", _o.isMain());
return d;
}
@ -60,6 +61,7 @@ public class BreakerSerializer extends AbstractDaoSerializer<Breaker>
o.setPolarity(DaoSerializer.getEnum(_d, "polarity", BreakerPolarity.class));
o.setDoublePower(DaoSerializer.getBoolean(_d, "double_power"));
o.setType(DaoSerializer.getEnum(_d, "type", BreakerType.class));
o.setMain(DaoSerializer.getBoolean(_d, "main"));
return o;
}
}

View File

@ -9,7 +9,6 @@ import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import java.util.TimeZone;
@ -38,6 +37,7 @@ public class EnergySummarySerializer extends AbstractDaoSerializer<EnergySummary
d.put("view_mode", DaoSerializer.toEnumName(_o.getViewMode()));
d.put("start", DaoSerializer.toLong(_o.getStart()));
d.put("sub_groups", DaoSerializer.toDaoEntities(_o.getSubGroups(), DaoProxyType.MONGO));
d.put("main", _o.isMain());
TimeZone tz = DateUtils.defaultTimeZone(_o.getTimeZone());
d.put("timezone", tz.getID());
if (_o.getEnergy() != null)
@ -61,6 +61,7 @@ public class EnergySummarySerializer extends AbstractDaoSerializer<EnergySummary
o.setViewMode(DaoSerializer.getEnum(_d, "view_mode", EnergyViewMode.class));
o.setStart(DaoSerializer.getDate(_d, "start"));
o.setSubGroups(DaoSerializer.getList(_d, "sub_groups", EnergySummary.class));
o.setMain(DaoSerializer.getBoolean(_d, "main"));
o.setTimeZone(DateUtils.fromTimeZoneId(DaoSerializer.getString(_d, "timezone")));
o.setEnergy(CollectionUtils.toFloatArray(DaoSerializer.getByteArray(_d, "energy")));
o.setGridEnergy(CollectionUtils.toFloatArray(DaoSerializer.getByteArray(_d, "grid_energy")));

View File

@ -12,33 +12,35 @@ public class PiGpioFactory {
private static boolean initialized = false;
public static Spi getSpiChannel(int _channel, int _baud, boolean _auxiliary) {
ensureInitialized();
if (!ensureInitialized())
return null;
int channelId = (0xff & _channel);
if (_auxiliary)
channelId |= 0x100;
Spi handle = spiHandles.get(channelId);
if (handle == null) {
int h = PIGPIO.spiOpen(_channel, _baud, _auxiliary ? 0x100 : 0);
if (h >= 0) {
handle = new Spi(h);
spiHandles.put(channelId, handle);
}
else {
LOG.error("Failed to get SPI handle");
}
if (handle != null)
return handle;
int h = PIGPIO.spiOpen(_channel, _baud, _auxiliary ? 0x100 : 0);
if (h >= 0) {
handle = new Spi(h);
spiHandles.put(channelId, handle);
return handle;
}
return handle;
LOG.error("Failed to get SPI handle");
return null;
}
private static void ensureInitialized() {
private static boolean ensureInitialized() {
if (initialized)
return;
return true;
int init = PIGPIO.gpioInitialise();
LOG.info("GPIO init: {}", init);
if (init < 0)
if (init < 0) {
LOG.error("Failed to initialize PiGpio");
else
initialized = true;
return false;
}
initialized = true;
return true;
}
public static void shutdown() {