Compare commits

..

21 Commits
1.0.7 ... main

Author SHA1 Message Date
Mark Milligan
95a554422d Synchronize around the wait call that keeps the service running. 2022-11-17 17:21:36 -06:00
Mark Milligan
bf648f069c Prevent an NPE if apple SSO is not configured on a self-hosted server. 2022-11-06 18:50:09 -06:00
Mark Milligan
07765061bd Add config flag for hubs to post to self-hosted server with a self-signed SSL certificate.
Support Raspberry Pi 2B by handling missing BLE controller and supporting 32-bit pigpio.
2022-11-06 18:21:54 -06:00
Mark Milligan
a8236cebc1 Merge branch 'main' of https://github.com/MarkBryanMilligan/LanternPowerMonitor into main 2022-10-22 14:36:35 -05:00
Mark Milligan
b14e6a086d Update BOM for out of stock parts 2022-10-22 14:35:40 -05:00
MarkBryanMilligan
bdf8652450
Update README.md 2022-08-21 09:26:58 -05:00
Mark Milligan
e0d4dafd3a Implement 3-phase voltage logic. 2022-08-20 21:20:50 -05:00
Mark Milligan
1369529e8d Fix the 3B+ lid size. 2022-08-08 17:31:27 -05:00
Mark Milligan
a23ea4d8b1 Update the non-flanged 3B+ case. 2022-08-08 17:20:08 -05:00
Mark Milligan
b0ffd93f8d Added an example of querying an energy summary for a day using the API. 2022-06-05 22:36:50 -05:00
Mark Milligan
b8acfd4ea4 Merge remote-tracking branch 'origin/main' into main 2022-05-22 23:14:08 -05:00
Mark Milligan
0f730aac32 Start work on tracking the mains separately. 2022-05-22 23:13:54 -05:00
MarkBryanMilligan
e37a4905db
Update README.md 2022-05-04 16:41:12 -05:00
Mark Milligan
d7edf3db4a Turns out we don't actually need 30MB of bloated jars to make a single HTTP post to get a Google SSO auth token. Don't need them for Firebase either. And not for Apple SSO. Shoot while we're at it, might as well get rid of pi4j too since making a JNI wrapper for PiGPio is easy enough. 2022-05-02 18:20:03 -05:00
Mark Milligan
c8319d6369 Migrating from from wiringpi to pigpio. This increases the sample rate by a factor of 3 and will allow creation of a board that can monitor over 30 breakers with a single raspberry pi.
This is based on pi4j 2.0 which is in a beta status.  I have fixed a few bugs in a local version of pi4j 2.0 to get it to work but I haven't submitted those changes to pi4j yet.

This change requires a migration to Java 11 and will *NOT* be backwards compatible.  Upgrading to this hub software will require that java 11 and pigpio be installed on the hub.  This can be done from an ssh session with the following commands:

apt-get update
apt-get install openjdk-11-jre-headless
apt-get install pigpio

Alternatively, you can download a new sd image, reflash your sd card, and re-adopt your hub.
2022-04-29 14:59:56 -05:00
MarkBryanMilligan
079206fcd7 Update the open source PCB to have better ground connections. 2022-04-08 13:28:54 -05:00
MarkBryanMilligan
d30fc4b4ce Fix a bug that was making it impossible to display billing information in the android app. 2022-04-04 17:20:15 -05:00
MarkBryanMilligan
8387216c44 Increase the clearances on the 4B case a little bit so you don't need a rubber mallet to get the Pi in there. 2022-03-10 15:05:08 -06:00
MarkBryanMilligan
119173f2d2 Move the config creation files to their own shaded jar so they can be run from the command line without an IDE or messy classpath specification. 2022-02-25 22:15:54 -06:00
MarkBryanMilligan
c916b25427 Force all console pages to SSL. 2022-02-17 15:26:32 -06:00
MarkBryanMilligan
dabefd1e7c Update the flanged cases for the 3B and 4B so the screws are recessed. 2022-02-12 15:17:30 -06:00
153 changed files with 18312 additions and 31406 deletions

View File

@ -13,6 +13,10 @@ The android application is available here:
<br>
[Lantern Power Monitor - Google Play](https://play.google.com/store/apps/details?id=com.lanternsoftware.lantern)
<br><br>
The iOS application is available here:
<br>
[Lantern Power Monitor - App Store](https://apps.apple.com/us/app/lantern-power-monitor/id1620735464)
<br><br>
The LanternPowerMonitor subreddit is a great place to ask questions and stay tuned for updates and news.
<br>
[/r/LanternPowerMonitor](https://www.reddit.com/r/LanternPowerMonitor/)
@ -45,15 +49,15 @@ This is only tangentially related. A java library for running a zwave controlle
# Ok, how do I run this thing?
The easiest way to run the software on a hub is to download a pre-built SD card image. One can be downloaded here:<br>
[hub_1.0.7.zip](https://cf.lanternpowermonitor.com/hub_1.0.7.zip)
[hub_1.1.1.zip](https://cf.lanternpowermonitor.com/hub_1.1.1.zip)
<br><br>
Flash this to any micro sd card (4gig or larger) and you're good to go. Fire up the hub and the phone app should be able to connect to it via bluetooth to finish the configuration. The default password on this image is pi/LanternPowerMonitor<br><br>
When you add the hub to your configuration via the app, you can change where the hub posts data. If you use lanternsoftware.com (the default host), your data will be stored there securely and won't be shared with or sold to anyone. If you really want to run your own server, you're of course welcome to do that instead, instructions are located further down.
When you add the hub to your configuration via the app, you can change where the hub posts data. If you use lanternpowermonitor.com (the default host), your data will be stored there securely and won't be shared with or sold to anyone. If you really want to run your own server, you're of course welcome to do that instead, instructions are located further down.
## Now that the service is running on the pi, how do I configure everything in the android app?
1. Create your panel in the "Configure Panels" page from the main menu. Before you have your hub connected, there will be no place to select a hub and port for each breaker. Don't worry, we'll get to that later.
1. With your hub plugged in and running for at least 30 seconds or so, go into the "Configure Hubs" page from the main menu. In here you'll see a status of "Scanning for Hubs..." (if you're on at least 1.0.7 of the app). If you're in range of your hub and its service is running, the app should find it pretty quickly (less than 15 seconds). If this is the first hub you've added, it will prompt you for your wifi credentials. After that, it will send via bluetooth the hub index (so it knows which hub it is), host (so it knows where to post data), auth code (so it knows which account it is and can post data), the encrypted wifi credentials, and finally a command to reboot.
1. With your hub plugged in and running for at least a minute, go into the "Configure Hubs" page from the main menu. In here you'll see a status of "Scanning for Hubs..." If you're in range of your hub and its service is running, the app should find it pretty quickly (less than 15 seconds). If this is the first hub you've added, it will prompt you for your wifi credentials. After that, it will send via bluetooth the hub index (so it knows which hub it is), host (so it knows where to post data), auth code (so it knows which account it is and can post data), the encrypted wifi credentials, and finally a command to reboot.
1. After your hub reboots, it should acquire an ip from your router, start the service, and try to start posting data. The hubs will try to auto-calibrate your voltage to 120V too, but if your AC/AC transformer is not plugged in, it will notice that and not try to auto-calibrate. It will continue to try to auto-calibrate each time you restart the hub until it does so succesfully.
@ -72,27 +76,17 @@ First, you and I will get along just fine. Second, do a reactor build from the
The compiled service will be at LanternPowerMonitor/currentmonitor/lantern-currentmonitor/target/lantern-currentmonitor.jar<br>
This is a shaded jar that contains all of the required components to function. It must be copied to /opt/currentmonitor on the pi.<br><br>
After that, you need to install wiring-pi:
After that, you need to install pigpio:
```
sudo apt-get install wiringpi
sudo apt-get install pigpio
```
You also need to have java 1.8 or newer installed.
Create a configuration file at /opt/currentmonitor/config.json<br>
Use the format below to get started
```
{
"hub": 0,
"host": "https://lanternsoftware.com/currentmonitor",
"auto_calibration_voltage": 120.0,
"needs_calibration": true
}
```
To install the current monitor service, use a service file like the one below:
```
[Unit]
Description=Current Monitor
After=syslog.target network.target
After=syslog.target network-online.target
Wants=network-online.target
[Service]
Type=simple
@ -119,7 +113,7 @@ After your reactor build, the compiled war will be at LanternPowerMonitor/curren
That can be deployed to tomcat. The 'host' parameter in the raspberry pi config.json file needs to point to wherever you deploy the service so your hubs post the data to your server instead of the official lantern software one.<br>
I'd recommend a valid dns entry and an ssl certificate, but, it's up to you, you're already knee deep in "I'll do what I want" territory here.<br><br>
Before you deploy it, you need to generate a config file that contains the mongodb credentials.<br>
There is a file at lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateMongoConfig.java that can do this for you.<br>
There is a file at lantern-config-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CreateMongoConfig.java that can do this for you.<br>
Place the generated config file in /opt/tomcat (which is where I have tomcat installed). If you want it to be read from somewhere else, you can modify the paths in LanternFiles.java<br><br>
The last thing you need is a private aes key to encrypt user auth tokens. One of those can be generated with CreateAuthKey.java.<br>
I realize these instructions aren't complete, but if you're going down this path, I suspect you sort of already know what you're doing, so hopefully that's enough to point you in the right direction.

Binary file not shown.

10
bom/LCSC_BOM.csv Normal file
View File

@ -0,0 +1,10 @@
LCSC Part Number,Quantity
C2977589,1
C43846,1
C43840,1
C385441,2
C385449,1
C385460,1
C385498,1
C433508,1
C397337,16
1 LCSC Part Number Quantity
2 C2977589 1
3 C43846 1
4 C43840 1
5 C385441 2
6 C385449 1
7 C385460 1
8 C385498 1
9 C433508 1
10 C397337 16

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,105 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>lantern-config-currentmonitor</artifactId>
<packaging>jar</packaging>
<version>1.1.0</version>
<name>lantern-config-currentmonitor</name>
<parent>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>currentmonitor</artifactId>
<version>1.1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.29</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-dao-mongo</artifactId>
<version>${util.version}</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-common</artifactId>
<version>${util.version}</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<optimize>true</optimize>
<showDeprecation>true</showDeprecation>
<encoding>UTF-8</encoding>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>lantern-config-currentmonitor</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>com.lanternsoftware.currentmonitor.CreateMongoConfig</Main-Class>
<Specification-Title>Lantern Power Monitor</Specification-Title>
<Specification-Version>${project.version}</Specification-Version>
<Specification-Vendor>Lantern Software, Inc.</Specification-Vendor>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,14 @@
package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
import com.lanternsoftware.util.external.LanternFiles;
public class CreateMongoConfig {
public static void main(String[] args) {
if (CollectionUtils.size(args) == 3)
new MongoConfig(args[0], args[1], args[2], "CURRENT_MONITOR").saveToDisk(LanternFiles.CONFIG_PATH + "mongo.cfg");
else
new MongoConfig("lanternsoftware.com", "*redacted*", "*redacted*", "CURRENT_MONITOR").saveToDisk(LanternFiles.CONFIG_PATH + "mongo.cfg");
}
}

View File

@ -1,15 +1,15 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>lantern-currentmonitor</artifactId>
<packaging>jar</packaging>
<version>1.0.7</version>
<version>1.1.3</version>
<name>lantern-currentmonitor</name>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>currentmonitor</artifactId>
<version>1.1.0</version>
</parent>
<dependencies>
<dependency>
@ -23,9 +23,9 @@
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.pi4j</groupId>
<artifactId>pi4j-gpio-extension</artifactId>
<version>1.3</version>
<groupId>com.lanternsoftware.pigpio</groupId>
<artifactId>lantern-pigpio</artifactId>
<version>${pigpio.version}</version>
</dependency>
<dependency>
<groupId>com.github.hypfvieh</groupId>
@ -35,12 +35,12 @@
<dependency>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>lantern-datamodel-currentmonitor</artifactId>
<version>1.0.0</version>
<version>${cm.version}</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-http</artifactId>
<version>1.0.0</version>
<version>${util.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.paho</groupId>
@ -58,7 +58,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<version>3.10.1</version>
<executions>
<execution>
<goals>
@ -71,14 +71,14 @@
<optimize>true</optimize>
<showDeprecation>true</showDeprecation>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<version>3.3.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>

View File

@ -7,27 +7,38 @@ import com.lanternsoftware.currentmonitor.bluetooth.BleHelper;
import com.lanternsoftware.currentmonitor.bluetooth.BleService;
import com.lanternsoftware.datamodel.currentmonitor.HubConfigService;
import com.lanternsoftware.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class BluetoothConfig {
private static final Logger LOG = LoggerFactory.getLogger(BluetoothConfig.class);
private final BleApplication app;
public BluetoothConfig(String _hubName, BleCharacteristicListener _listener) {
BleHelper.getAdapter().setPowered(true);
BleHelper.requestBusName("com.lanternsoftware");
BleHelper.setBasePath("/com/lanternsoftware");
HubConfigService service = new HubConfigService();
List<BleCharacteristic> chars = CollectionUtils.transform(service.getCharacteristics(), _c->new BleCharacteristic("HubConfig", _c.getUUID(), _c.name(), _c.getFlags()));
chars.forEach(_c->_c.setListener(_listener));
app = new BleApplication("Lantern", _hubName, new BleService("HubConfig", service.getServiceUUID(), chars));
BleApplication a = null;
try {
BleHelper.getAdapter().setPowered(true);
BleHelper.requestBusName("com.lanternsoftware");
BleHelper.setBasePath("/com/lanternsoftware");
List<BleCharacteristic> chars = CollectionUtils.transform(HubConfigService.getCharacteristics(), _c -> new BleCharacteristic("HubConfig", _c.getUUID(), _c.name(), _c.getFlags()));
chars.forEach(_c -> _c.setListener(_listener));
a = new BleApplication("Lantern", _hubName, new BleService("HubConfig", HubConfigService.getServiceUUID(), chars));
}
catch (Throwable _t) {
LOG.error("Failed to initialize BLE service", _t);
}
app = a;
}
public void start() {
app.start();
if (app != null)
app.start();
}
public void stop() {
app.stop();
if (app != null)
app.stop();
}
}

View File

@ -1,18 +1,20 @@
package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.currentmonitor.adc.MCP3008Pin;
import com.lanternsoftware.datamodel.currentmonitor.Breaker;
import com.pi4j.io.gpio.GpioPinAnalogInput;
import com.lanternsoftware.datamodel.currentmonitor.hub.PowerSample;
import java.util.List;
public class BreakerSamples {
private final Breaker breaker;
private final GpioPinAnalogInput voltagePin;
private final GpioPinAnalogInput currentPin;
private final MCP3008Pin voltagePin;
private final MCP3008Pin currentPin;
private final List<PowerSample> samples;
private int cycleCnt;
private int sampleCnt;
public BreakerSamples(Breaker _breaker, GpioPinAnalogInput _voltagePin, GpioPinAnalogInput _currentPin, List<PowerSample> _samples) {
public BreakerSamples(Breaker _breaker, MCP3008Pin _voltagePin, MCP3008Pin _currentPin, List<PowerSample> _samples) {
breaker = _breaker;
voltagePin = _voltagePin;
currentPin = _currentPin;
@ -23,11 +25,11 @@ public class BreakerSamples {
return breaker;
}
public GpioPinAnalogInput getVoltagePin() {
public MCP3008Pin getVoltagePin() {
return voltagePin;
}
public GpioPinAnalogInput getCurrentPin() {
public MCP3008Pin getCurrentPin() {
return currentPin;
}
@ -35,8 +37,20 @@ public class BreakerSamples {
return samples;
}
public PowerSample getSample(int _sample) {
return samples.get(_sample);
public void incrementCycleCnt() {
cycleCnt++;
}
public PowerSample incrementSample() {
return samples.get(sampleCnt++);
}
public int getCycleCnt() {
return cycleCnt;
}
public void setCycleCnt(int _cycleCnt) {
cycleCnt = _cycleCnt;
}
public int getSampleCnt() {

View File

@ -1,51 +1,55 @@
package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.currentmonitor.adc.MCP3008;
import com.lanternsoftware.currentmonitor.adc.MCP3008Pin;
import com.lanternsoftware.datamodel.currentmonitor.Breaker;
import com.lanternsoftware.datamodel.currentmonitor.BreakerHub;
import com.lanternsoftware.datamodel.currentmonitor.BreakerPolarity;
import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
import com.lanternsoftware.datamodel.currentmonitor.hub.BreakerSample;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
import com.lanternsoftware.datamodel.currentmonitor.hub.PowerSample;
import com.lanternsoftware.pigpio.PiGpioFactory;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.concurrency.ConcurrencyUtils;
import com.pi4j.gpio.extension.base.AdcGpioProvider;
import com.pi4j.gpio.extension.mcp.MCP3008GpioProvider;
import com.pi4j.gpio.extension.mcp.MCP3008Pin;
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinAnalogInput;
import com.pi4j.io.spi.SpiChannel;
import com.pi4j.io.spi.SpiDevice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CurrentMonitor {
private static final Logger LOG = LoggerFactory.getLogger(CurrentMonitor.class);
private static final int BATCH_CNT = 4;
private GpioController gpio;
private final ExecutorService executor = Executors.newCachedThreadPool();
private final Map<Integer, AdcGpioProvider> chips = new HashMap<>();
private final Map<Integer, GpioPinAnalogInput> pins = new HashMap<>();
private final Map<Integer, MCP3008> chips = new HashMap<>();
private Sampler sampler;
private PowerListener listener;
private boolean debug = false;
private boolean postSamples = false;
public void start() {
try {
gpio = GpioFactory.getInstance();
LOG.info("Current Monitor Started");
}
catch (Throwable t) {
LOG.info("Failed to get gpio factory", t);
}
public boolean isDebug() {
return debug;
}
public void setDebug(boolean _debug) {
debug = _debug;
}
public boolean isPostSamples() {
return postSamples;
}
public void setPostSamples(boolean _postSamples) {
postSamples = _postSamples;
}
public void stop() {
@ -53,21 +57,15 @@ public class CurrentMonitor {
ConcurrencyUtils.sleep(1000);
executor.shutdown();
ConcurrencyUtils.sleep(1000);
PiGpioFactory.shutdown();
chips.clear();
pins.clear();
gpio.shutdown();
LOG.info("Power Monitor Service Stopped");
}
public void setDebug(boolean _debug) {
debug = _debug;
}
public CalibrationResult calibrateVoltage(double _curCalibration) {
GpioPinAnalogInput voltagePin = getPin(0, 0);
if (voltagePin == null)
return null;
int maxSamples = 120000;
LOG.info("Calibrating Voltage");
MCP3008Pin voltagePin = new MCP3008Pin(getChip(0), 0);
int maxSamples = 240000;
CalibrationSample[] samples = new CalibrationSample[maxSamples];
int offset = 0;
for (;offset < maxSamples; offset++) {
@ -77,7 +75,7 @@ public class CurrentMonitor {
long intervalEnd = System.nanoTime() + 2000000000L; //Scan voltage for 2 seconds
while (offset < maxSamples) {
samples[offset].time = System.nanoTime();
samples[offset].voltage = voltagePin.getValue();
samples[offset].voltage = voltagePin.read();
offset++;
if (samples[offset-1].time > intervalEnd)
break;
@ -128,10 +126,12 @@ public class CurrentMonitor {
stopMonitoring();
listener = _listener;
List<Breaker> validBreakers = CollectionUtils.filter(_breakers, _b -> _b.getPort() > 0 && _b.getPort() < 16);
if (CollectionUtils.isEmpty(validBreakers))
if (CollectionUtils.isEmpty(validBreakers)) {
LOG.error("No breakers found for hub number {}", _hub.getHub());
return;
}
LOG.info("Monitoring {} breakers for hub {}", CollectionUtils.size(validBreakers), _hub.getHub());
sampler = new Sampler(_hub, validBreakers, _intervalMs, 2);
sampler = new Sampler(_hub, validBreakers, _intervalMs, 5);
LOG.info("Starting to monitor ports {}", CollectionUtils.transformToCommaSeparated(validBreakers, _b -> String.valueOf(_b.getPort())));
executor.submit(sampler);
}
@ -140,34 +140,15 @@ public class CurrentMonitor {
}
}
private GpioPinAnalogInput getPin(int _chip, int _pin) {
GpioPinAnalogInput pin;
synchronized (pins) {
AdcGpioProvider chip = chips.get(_chip);
if (chip == null) {
SpiChannel channel = SpiChannel.getByNumber(_chip);
if (channel == null)
return null;
try {
chip = new MCP3008GpioProvider(channel, 1250000, SpiDevice.DEFAULT_SPI_MODE, false);
chips.put(_chip, chip);
} catch (IOException _e) {
LOG.error("Failed to connect to chip {}", _chip, _e);
return null;
}
}
int pinKey = pinKey(_chip, _pin);
pin = pins.get(pinKey);
if (pin == null) {
pin = gpio.provisionAnalogInputPin(chip, MCP3008Pin.ALL[_pin], String.valueOf(pinKey));
pins.put(pinKey, pin);
}
private synchronized MCP3008 getChip(int _chip) {
MCP3008 chip = chips.get(_chip);
if (chip == null) {
String id = "SPI" + _chip;
LOG.info("Creating chip {}", id);
chip = new MCP3008(PiGpioFactory.getSpiChannel(_chip, 810000, false));
chips.put(_chip, chip);
}
return pin;
}
private Integer pinKey(int _chip, int _pin) {
return (_chip*8)+_pin;
return chip;
}
public void submit(Runnable _runnable) {
@ -185,19 +166,19 @@ public class CurrentMonitor {
private boolean running = true;
private final BreakerHub hub;
private final List<List<BreakerSamples>> breakers;
private final int intervalNs;
private final long intervalNs;
private final int concurrentBreakerCnt;
public Sampler(BreakerHub _hub, List<Breaker> _breakers, int _intervalMs, int _concurrentBreakerCnt) {
public Sampler(BreakerHub _hub, List<Breaker> _breakers, long _intervalMs, int _concurrentBreakerCnt) {
hub = _hub;
GpioPinAnalogInput voltagePin = getPin(0, 0);
MCP3008Pin voltagePin = new MCP3008Pin(getChip(0), 0);
breakers = CollectionUtils.transform(_breakers, _b->{
LOG.info("Getting Chip {}, Pin {} for port {}", _b.getChip(), _b.getPin(), _b.getPort());
GpioPinAnalogInput currentPin = getPin(_b.getChip(), _b.getPin());
MCP3008Pin currentPin = new MCP3008Pin(getChip(_b.getChip()), _b.getPin());
List<BreakerSamples> batches = new ArrayList<>(BATCH_CNT);
for (int i=0; i<BATCH_CNT; i++) {
List<PowerSample> samples = new ArrayList<>(30000/_breakers.size());
for (int j=0; j<30000/_breakers.size(); j++) {
for (int j=0; j<60000/_breakers.size(); j++) {
samples.add(new PowerSample());
}
batches.add(new BreakerSamples(_b, voltagePin, currentPin, samples));
@ -213,6 +194,11 @@ public class CurrentMonitor {
long start = System.nanoTime();
long interval = 0;
int cycle;
int curBreaker;
long intervalStart;
long intervalEnd;
long cycleEnd;
long curTime;
BreakerSamples[] cycleBreakers = new BreakerSamples[concurrentBreakerCnt];
try {
while (true) {
@ -223,36 +209,83 @@ public class CurrentMonitor {
}
}
final Date readTime = new Date();
final long intervalStart = (interval * intervalNs) + start;
long intervalEnd = intervalStart + intervalNs;
intervalStart = (interval * intervalNs) + start;
intervalEnd = intervalStart + intervalNs;
cycle = 0;
final int batch = (int) (interval % BATCH_CNT);
int curBreaker;
for (curBreaker = 0; curBreaker < breakers.size(); curBreaker++) {
breakers.get(curBreaker).get(batch).setSampleCnt(0);
}
while (System.nanoTime() < intervalEnd) {
for (curBreaker = 0; curBreaker < concurrentBreakerCnt; curBreaker++) {
cycleBreakers[curBreaker] = breakers.get(((cycle * concurrentBreakerCnt) + curBreaker) % breakers.size()).get(batch);
cycleBreakers[curBreaker].incrementCycleCnt();
}
cycle++;
long cycleEnd = intervalStart + (cycle * (intervalNs / hub.getFrequency()));
while (System.nanoTime() < cycleEnd) {
cycleEnd = intervalStart + (cycle * (intervalNs / hub.getFrequency()));
curTime = System.nanoTime();
while (curTime < cycleEnd) {
for (curBreaker = 0; curBreaker < concurrentBreakerCnt; curBreaker++) {
PowerSample sample = cycleBreakers[curBreaker].getSample(cycleBreakers[curBreaker].getSampleCnt());
sample.voltage = cycleBreakers[curBreaker].getVoltagePin().getValue();
sample.current = cycleBreakers[curBreaker].getCurrentPin().getValue();
cycleBreakers[curBreaker].setSampleCnt(cycleBreakers[curBreaker].getSampleCnt()+1);
PowerSample sample = cycleBreakers[curBreaker].incrementSample();
sample.nanoTime = curTime;
sample.cycle = cycle;
sample.voltage = cycleBreakers[curBreaker].getVoltagePin().read();
sample.current = cycleBreakers[curBreaker].getCurrentPin().read();
}
curTime = System.nanoTime();
}
}
interval++;
final HubSample hubSample = (postSamples && (interval == 10)) ? new HubSample() : null;
executor.submit(() -> {
long cycleLength = 1000000000/hub.getFrequency();
if (hubSample != null) {
hubSample.setSampleDate(new Date());
hubSample.setBreakers(new ArrayList<>());
}
for (List<BreakerSamples> breaker : breakers) {
double vOffset = 0.0;
double iOffset = 0.0;
BreakerSamples samples = breaker.get(batch);
List<PowerSample> validSamples = samples.getSamples().subList(0, samples.getSampleCnt());
if (hubSample != null) {
BreakerSample breakerSample = new BreakerSample();
breakerSample.setSamples(validSamples);
breakerSample.setPanel(samples.getBreaker().getPanel());
breakerSample.setSpace(samples.getBreaker().getSpace());
hubSample.getBreakers().add(breakerSample);
}
int phaseOffsetNs = samples.getBreaker().getPhaseOffsetNs()-hub.getPhaseOffsetNs();
if (phaseOffsetNs != 0) {
Map<Integer, List<PowerSample>> cycles = CollectionUtils.transformToMultiMap(validSamples, _p->_p.cycle);
for (List<PowerSample> cycleSamples : cycles.values()) {
long minNano;
long maxNano = minNano = cycleSamples.get(0).nanoTime;
for (PowerSample sample : cycleSamples) {
if (sample.nanoTime < minNano)
minNano = sample.nanoTime;
if (sample.nanoTime > maxNano)
maxNano = sample.nanoTime;
}
TreeMap<Long, Double> offsetSamples = new TreeMap<>();
for (PowerSample sample : cycleSamples) {
if (sample.nanoTime + phaseOffsetNs < minNano)
offsetSamples.put(sample.nanoTime + phaseOffsetNs + cycleLength, sample.voltage);
else if (sample.nanoTime + phaseOffsetNs > maxNano)
offsetSamples.put(sample.nanoTime + phaseOffsetNs - cycleLength, sample.voltage);
else
offsetSamples.put(sample.nanoTime + phaseOffsetNs, sample.voltage);
}
for (PowerSample sample : cycleSamples) {
List<Double> voltages = new ArrayList<>();
Entry<Long, Double> floorEntry = offsetSamples.floorEntry(sample.nanoTime);
if (floorEntry != null)
voltages.add(floorEntry.getValue());
Entry<Long, Double> ceilingEntry = offsetSamples.ceilingEntry(sample.nanoTime);
if (ceilingEntry != null)
voltages.add(ceilingEntry.getValue());
sample.voltage = CollectionUtils.mean(voltages);
}
}
}
double vOffset = 0.0;
double iOffset = 0.0;
for (PowerSample sample : validSamples) {
vOffset += sample.voltage;
iOffset += sample.current;
@ -263,6 +296,7 @@ public class CurrentMonitor {
double pSum = 0.0;
double vRms = 0.0;
double lowPassFilter = samples.getBreaker().getLowPassFilter();
for (PowerSample sample : validSamples) {
sample.current -= iOffset;
if (Math.abs(sample.current) < lowPassFilter)
@ -273,21 +307,26 @@ public class CurrentMonitor {
}
vRms /= validSamples.size();
vRms = hub.getVoltageCalibrationFactor() * Math.sqrt(vRms);
int lowSampleRatio = (lowSamples * 100) / samples.getSampleCnt();
double realPower = Math.abs((hub.getVoltageCalibrationFactor() * hub.getPortCalibrationFactor() * samples.getBreaker().getFinalCalibrationFactor() * pSum) / samples.getSampleCnt());
if ((lowSampleRatio > 75) && realPower < 13.0)
int lowSampleRatio = (lowSamples * 100) / validSamples.size();
double realPower = (hub.getVoltageCalibrationFactor() * hub.getPortCalibrationFactor() * samples.getBreaker().getFinalCalibrationFactor() * pSum) / validSamples.size();
if ((lowSampleRatio > 75) && Math.abs(realPower) < 13.0)
realPower = 0.0;
if (samples.getBreaker().getPolarity() == BreakerPolarity.SOLAR)
if (samples.getBreaker().getPolarity() == BreakerPolarity.NORMAL)
realPower = Math.abs(realPower);
else if (samples.getBreaker().getPolarity() == BreakerPolarity.SOLAR)
realPower = -Math.abs(realPower);
else if (samples.getBreaker().getPolarity() == BreakerPolarity.BI_DIRECTIONAL_INVERTED)
realPower = -realPower;
if (samples.getBreaker().isDoublePower())
realPower *= 2.0;
if (debug) {
synchronized (CurrentMonitor.this) {
LOG.info("===========================Start Port {}", samples.getBreaker().getPort());
LOG.info("Cycles: {}", samples.getCycleCnt());
LOG.info("Samples: {}", samples.getSampleCnt());
LOG.info("vMin: {}, vMax: {}, vOffset: {}", String.format("%.3f", CollectionUtils.getSmallest(validSamples, Comparator.comparing(_v -> _v.voltage)).voltage), String.format("%.3f", CollectionUtils.getLargest(validSamples, Comparator.comparing(_v -> _v.voltage)).voltage), String.format("%.3f", vOffset));
LOG.info("iMin: {}, iMax: {}, iOffset: {}", String.format("%.3f", CollectionUtils.getSmallest(validSamples, Comparator.comparing(_v -> _v.current)).current), String.format("%.3f", CollectionUtils.getLargest(validSamples, Comparator.comparing(_v -> _v.current)).current), String.format("%.3f", iOffset));
double iRms = samples.getBreaker().getFinalCalibrationFactor() * Math.sqrt(CollectionUtils.mean(CollectionUtils.transform(validSamples, _p -> _p.current * _p.current)));
double iRms = hub.getPortCalibrationFactor() * samples.getBreaker().getFinalCalibrationFactor() * Math.sqrt(CollectionUtils.mean(CollectionUtils.transform(validSamples, _p -> _p.current * _p.current)));
LOG.info("vRms: {}", String.format("%.3f", vRms));
LOG.info("iRms: {}", String.format("%.3f", iRms));
double apparentPower = vRms * iRms;
@ -298,8 +337,12 @@ public class CurrentMonitor {
LOG.info("===========================End Port {}", samples.getBreaker().getPort());
}
}
samples.setSampleCnt(0);
samples.setCycleCnt(0);
listener.onPowerEvent(new BreakerPower(samples.getBreaker().getPanel(), samples.getBreaker().getSpace(), readTime, realPower, vRms));
}
if (hubSample != null)
listener.onSampleEvent(hubSample);
});
}
}

View File

@ -16,6 +16,7 @@ import com.lanternsoftware.datamodel.currentmonitor.HubConfigCharacteristic;
import com.lanternsoftware.datamodel.currentmonitor.HubConfigService;
import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
import com.lanternsoftware.datamodel.currentmonitor.NetworkStatus;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.ResourceLoader;
@ -35,12 +36,9 @@ import org.apache.http.entity.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.Console;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -68,16 +66,24 @@ public class MonitorApp {
private static final CurrentMonitor monitor = new CurrentMonitor();
private static final List<BreakerPower> readings = new ArrayList<>();
private static String version;
private static final PowerListener logger = _p -> {
if (!config.isDebug()) {
_p.setHubVersion(version);
if (breakerConfig != null)
_p.setAccountId(breakerConfig.getAccountId());
synchronized (readings) {
readings.add(_p);
}
} else
LOG.info("Panel{} - Space{} Power: {}W", _p.getPanel(), Breaker.toSpaceDisplay(_p.getSpace()), String.format("%.3f", _p.getPower()));
private static final PowerListener logger = new PowerListener() {
@Override
public void onPowerEvent(BreakerPower _power) {
if (!config.isDebug()) {
_power.setHubVersion(version);
if (breakerConfig != null)
_power.setAccountId(breakerConfig.getAccountId());
synchronized (readings) {
readings.add(_power);
}
} else
LOG.info("Panel{} - Space{} Power: {}W", _power.getPanel(), Breaker.toSpaceDisplay(_power.getSpace()), String.format("%.3f", _power.getPower()));
}
@Override
public void onSampleEvent(HubSample _sample) {
post(DaoSerializer.toZipBson(_sample), "sample");
}
};
private static final BleCharacteristicListener bluetoothListener = new BleCharacteristicListener() {
@Override
@ -210,17 +216,15 @@ public class MonitorApp {
version = getVersionNumber();
config = DaoSerializer.parse(ResourceLoader.loadFileAsString(WORKING_DIR + "config.json"), MonitorConfig.class);
if (config == null) {
LOG.error("Failed to load config file from {}", WORKING_DIR + "config.json");
return;
config = new MonitorConfig();
ResourceLoader.writeFile(WORKING_DIR + "config.json", DaoSerializer.toJson(config));
}
pool = new HttpPool(10, 10, config.getSocketTimeout(), config.getConnectTimeout(), config.getSocketTimeout());
pool = HttpPool.builder().withValidateSSLCertificates(!config.isAcceptSelfSignedCertificates()).build();
if (NullUtils.isNotEmpty(config.getHost()))
host = NullUtils.terminateWith(config.getHost(), "/");
monitor.setDebug(config.isDebug());
monitor.start();
monitor.setPostSamples(config.isPostSamples());
LEDFlasher.setLEDOn(false);
bluetoothConfig = new BluetoothConfig("Lantern Hub", bluetoothListener);
bluetoothConfig.start();
if (NullUtils.isNotEmpty(config.getAuthCode()))
authCode = config.getAuthCode();
else if (NullUtils.isNotEmpty(host) && NullUtils.isNotEmpty(config.getUsername()) && NullUtils.isNotEmpty(config.getPassword())) {
@ -231,7 +235,8 @@ public class MonitorApp {
if (NullUtils.isNotEmpty(config.getMqttBrokerUrl()))
mqttPoster = new MqttPoster(config);
if (NullUtils.isNotEmpty(host) && NullUtils.isNotEmpty(authCode)) {
while (true) {
int configAttempts = 0;
while (configAttempts < 5) {
HttpGet get = new HttpGet(host + "config");
get.addHeader("auth_code", authCode);
breakerConfig = DaoSerializer.parse(pool.executeToString(get), BreakerConfig.class);
@ -239,8 +244,11 @@ public class MonitorApp {
break;
LOG.error("Failed to load breaker config. Retrying in 5 seconds...");
ConcurrencyUtils.sleep(5000);
configAttempts++;
}
}
bluetoothConfig = new BluetoothConfig("Lantern Hub", bluetoothListener);
bluetoothConfig.start();
if ((mqttPoster != null) && (breakerConfig == null)) {
LOG.info("Hub not configured by a Lantern Power Monitor server, defaulting to MQTT mode only");
BreakerHub hub = new BreakerHub();
@ -266,13 +274,18 @@ public class MonitorApp {
BreakerHub hub = breakerConfig.getHub(config.getHub());
if (hub != null) {
if (config.isNeedsCalibration()) {
CalibrationResult cal = monitor.calibrateVoltage(hub.getVoltageCalibrationFactor());
if (cal != null) {
hub.setVoltageCalibrationFactor(cal.getVoltageCalibrationFactor());
hub.setFrequency(cal.getFrequency());
config.setNeedsCalibration(false);
ResourceLoader.writeFile(WORKING_DIR + "config.json", DaoSerializer.toJson(config));
post(DaoSerializer.toZipBson(breakerConfig), "config");
try {
CalibrationResult cal = monitor.calibrateVoltage(hub.getVoltageCalibrationFactor());
if (cal != null) {
hub.setVoltageCalibrationFactor(cal.getVoltageCalibrationFactor());
hub.setFrequency(cal.getFrequency());
config.setNeedsCalibration(false);
ResourceLoader.writeFile(WORKING_DIR + "config.json", DaoSerializer.toJson(config));
post(DaoSerializer.toZipBson(breakerConfig), "config");
}
}
catch (Throwable t) {
LOG.error("Exception trying to read from voltage pin", t);
}
}
List<Breaker> breakers = breakerConfig.getBreakersForHub(config.getHub());
@ -288,17 +301,11 @@ public class MonitorApp {
monitor.stop();
pool.shutdown();
}, "Monitor Shutdown"));
Console c = System.console();
BufferedReader reader = (c == null)?new BufferedReader(new InputStreamReader(System.in)):null;
while (running.get()) {
synchronized (monitor) {
try {
String command = c != null ? c.readLine() : reader.readLine();
if (NullUtils.isEqual("exit", command))
break;
}
catch (Exception _e) {
LOG.error("Exception while reading from console input", _e);
break;
monitor.wait();
} catch (InterruptedException _e) {
LOG.error("Interrupted, shutting down", _e);
}
}
}
@ -360,7 +367,7 @@ public class MonitorApp {
byte[] payload = DaoSerializer.toZipBson(minutePost);
if (!post(payload, "power/hub")) {
LOG.info("Failed Posting HubPowerMinute, writing cache");
ResourceLoader.writeFile(WORKING_DIR + "cache/" + UUID.randomUUID().toString() + ".min", payload);
ResourceLoader.writeFile(WORKING_DIR + "cache/" + UUID.randomUUID() + ".min", payload);
}
}
if (post != null) {

View File

@ -2,6 +2,7 @@ package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.datamodel.currentmonitor.Breaker;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import java.util.List;
@ -12,11 +13,13 @@ public class MonitorConfig {
private String authCode;
private String username;
private String password;
private int hub;
private int hub = -1;
private boolean debug;
private int connectTimeout;
private int socketTimeout;
private boolean needsCalibration;
private boolean postSamples = false;
private boolean needsCalibration = true;
private boolean acceptSelfSignedCertificates = false;
private String mqttBrokerUrl;
private String mqttUserName;
private String mqttPassword;
@ -34,7 +37,7 @@ public class MonitorConfig {
}
public String getHost() {
return host;
return NullUtils.isEmpty(host) ? "https://lanternpowermonitor.com/currentmonitor/" : host;
}
public void setHost(String _host) {
@ -97,6 +100,14 @@ public class MonitorConfig {
socketTimeout = _socketTimeout;
}
public boolean isPostSamples() {
return postSamples;
}
public void setPostSamples(boolean _postSamples) {
postSamples = _postSamples;
}
public boolean isNeedsCalibration() {
return needsCalibration;
}
@ -105,6 +116,14 @@ public class MonitorConfig {
needsCalibration = _needsCalibration;
}
public boolean isAcceptSelfSignedCertificates() {
return acceptSelfSignedCertificates;
}
public void setAcceptSelfSignedCertificates(boolean _acceptSelfSignedCertificates) {
acceptSelfSignedCertificates = _acceptSelfSignedCertificates;
}
public String getMqttBrokerUrl() {
return mqttBrokerUrl;
}

View File

@ -1,7 +1,9 @@
package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
public interface PowerListener {
void onPowerEvent(BreakerPower _power);
void onSampleEvent(HubSample _sample);
}

View File

@ -1,6 +0,0 @@
package com.lanternsoftware.currentmonitor;
public class PowerSample {
public double voltage;
public double current;
}

View File

@ -0,0 +1,29 @@
package com.lanternsoftware.currentmonitor.adc;
import com.lanternsoftware.pigpio.Spi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MCP3008 {
protected static final Logger LOG = LoggerFactory.getLogger(MCP3008.class);
private static final byte[][] pins = new byte[8][];
private final Spi spi;
private final byte[] resp = new byte[3];
static {
for (int p = 0; p < 8; p++) {
pins[p] = new byte[]{1,(byte)(p + 8 << 4),0};
}
}
public MCP3008(Spi _spi) {
spi = _spi;
}
public int readPin(int _pin) {
if (spi != null && spi.transfer(pins[_pin], resp) > 2)
return ((resp[1] & 0x03) << 8) + (resp[2] & 0xFF);
return 0;
}
}

View File

@ -0,0 +1,15 @@
package com.lanternsoftware.currentmonitor.adc;
public class MCP3008Pin {
private final MCP3008 chip;
private final int pin;
public MCP3008Pin(MCP3008 _chip, int _pin) {
chip = _chip;
pin = _pin;
}
public int read() {
return chip.readPin(pin);
}
}

View File

@ -34,7 +34,9 @@ public class MonitorConfigSerializer extends AbstractDaoSerializer<MonitorConfig
d.put("debug", _o.isDebug());
d.put("connect_timeout", _o.getConnectTimeout());
d.put("socket_timeout", _o.getSocketTimeout());
d.put("post_samples", _o.isPostSamples());
d.put("needs_calibration", _o.isNeedsCalibration());
d.put("accept_self_signed_certificates", _o.isAcceptSelfSignedCertificates());
d.put("mqtt_broker_url", _o.getMqttBrokerUrl());
d.put("mqtt_user_name", _o.getMqttUserName());
d.put("mqtt_password", _o.getMqttPassword());
@ -57,7 +59,9 @@ public class MonitorConfigSerializer extends AbstractDaoSerializer<MonitorConfig
o.setDebug(DaoSerializer.getBoolean(_d, "debug"));
o.setConnectTimeout(DaoSerializer.getInteger(_d, "connect_timeout"));
o.setSocketTimeout(DaoSerializer.getInteger(_d, "socket_timeout"));
o.setPostSamples(DaoSerializer.getBoolean(_d, "post_samples"));
o.setNeedsCalibration(DaoSerializer.getBoolean(_d, "needs_calibration"));
o.setAcceptSelfSignedCertificates(DaoSerializer.getBoolean(_d, "accept_self_signed_certificates"));
o.setMqttBrokerUrl(DaoSerializer.getString(_d, "mqtt_broker_url"));
o.setMqttUserName(DaoSerializer.getString(_d, "mqtt_user_name"));
o.setMqttPassword(DaoSerializer.getString(_d, "mqtt_password"));

View File

@ -2,12 +2,6 @@
<configuration>
<property name="log.pattern" value="%date %-5level %logger{0} - %message%n"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/opt/currentmonitor/log/log.txt</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
@ -21,6 +15,7 @@
</appender>
<logger name="com.lanternsoftware" level="INFO"/>
<logger name="com.pi4j" level="INFO"/>
<root level="OFF">
<appender-ref ref="FILE"/>

View File

@ -1,31 +1,31 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>lantern-dataaccess-currentmonitor</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<version>1.1.0</version>
<name>lantern-dataaccess-currentmonitor</name>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>currentmonitor</artifactId>
<version>1.1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>lantern-datamodel-currentmonitor</artifactId>
<version>1.0.0</version>
<version>${cm.version}</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.rules</groupId>
<artifactId>lantern-datamodel-rules</artifactId>
<version>1.0.0</version>
<version>${rules.version}</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-dao-mongo</artifactId>
<version>1.0.0</version>
<version>${util.version}</version>
</dependency>
<dependency>
<groupId>org.mindrot</groupId>
@ -43,7 +43,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<version>3.10.1</version>
<executions>
<execution>
<goals>
@ -56,14 +56,14 @@
<optimize>true</optimize>
<showDeprecation>true</showDeprecation>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.4</version>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
@ -73,16 +73,6 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<index>true</index>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -4,22 +4,31 @@ import com.lanternsoftware.datamodel.currentmonitor.Account;
import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.DebugTimer;
import com.lanternsoftware.util.concurrency.ConcurrencyUtils;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoQuery;
import com.lanternsoftware.util.dao.DaoSort;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class BackupMinutes {
public static void main(String[] args) {
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
CurrentMonitorDao backupDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_DEST_PATH + "mongo.cfg"));
Date now = new Date();
for (Account a : dao.getProxy().queryAll(Account.class)) {
CurrentMonitorDao sourceDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
CurrentMonitorDao destDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_DEST_PATH + "mongo.cfg"));
ExecutorService ex = Executors.newFixedThreadPool(8);
List<Future<?>> tasks = new ArrayList<>();
for (Account a : sourceDao.getProxy().queryAll(Account.class)) {
if (a.getId() == 0)
continue;
DebugTimer t = new DebugTimer("Account " + a.getId());
@ -27,28 +36,35 @@ public class BackupMinutes {
a.setTimezone("America/Chicago");
}
TimeZone tz = TimeZone.getTimeZone(a.getTimezone());
HubPowerMinute minute = dao.getProxy().queryOne(HubPowerMinute.class, new DaoQuery("account_id", a.getId()), DaoSort.sort("minute"));
if (minute == null)
HubPowerMinute firstMinute = sourceDao.getProxy().queryOne(HubPowerMinute.class, new DaoQuery("account_id", a.getId()), DaoSort.sort("minute"));
if (firstMinute == null)
continue;
HubPowerMinute lastBackup = backupDao.getProxy().queryOne(HubPowerMinute.class, new DaoQuery("account_id", a.getId()), DaoSort.sortDesc("minute"));
Date start = lastBackup == null ? DateUtils.getMidnightBefore(minute.getMinuteAsDate(), tz) : lastBackup.getMinuteAsDate();
// Date start = DateUtils.date(10,16,2021,tz);
HubPowerMinute lastMinute = sourceDao.getProxy().queryOne(HubPowerMinute.class, new DaoQuery("account_id", a.getId()), DaoSort.sortDesc("minute"));
HubPowerMinute lastBackup = destDao.getProxy().queryOne(HubPowerMinute.class, new DaoQuery("account_id", a.getId()), DaoSort.sortDesc("minute"));
Date start = lastBackup == null ? DateUtils.getMidnightBefore(firstMinute.getMinuteAsDate(), tz) : lastBackup.getMinuteAsDate();
Date lastMin = lastMinute.getMinuteAsDate();
Date end = DateUtils.addDays(start, 1, tz);
while (start.before(now)) {
DebugTimer t2 = new DebugTimer("Account Id: " + a.getId() + " Query Day " + DateUtils.format("MM/dd/yyyy", tz, start));
List<HubPowerMinute> minutes = dao.getProxy().query(HubPowerMinute.class, new DaoQuery("account_id", a.getId()).andBetweenInclusiveExclusive("minute", (int) (start.getTime() / 60000), (int) (end.getTime() / 60000)));
t2.stop();
if (!minutes.isEmpty()) {
DebugTimer t3 = new DebugTimer("Save Day");
backupDao.getProxy().save(minutes);
t3.stop();
}
while (start.before(lastMin)) {
final Date curStart = start;
final Date curEnd = end;
tasks.add(ex.submit(() -> {
DebugTimer t2 = new DebugTimer("Account Id: " + a.getId() + " Query Day " + DateUtils.format("MM/dd/yyyy", tz, curStart));
List<HubPowerMinute> minutes = sourceDao.getProxy().query(HubPowerMinute.class, new DaoQuery("account_id", a.getId()).andBetweenInclusiveExclusive("minute", (int) (curStart.getTime() / 60000), (int) (curEnd.getTime() / 60000)));
t2.stop();
if (!minutes.isEmpty()) {
DebugTimer t3 = new DebugTimer("Save Day");
destDao.getProxy().save(minutes);
t3.stop();
}
}));
start = end;
end = DateUtils.addDays(end, 1, tz);
}
t.stop();
}
dao.shutdown();
backupDao.shutdown();
ConcurrencyUtils.getAll(tasks);
ex.shutdown();
sourceDao.shutdown();
destDao.shutdown();
}
}

View File

@ -8,6 +8,7 @@ import com.lanternsoftware.datamodel.currentmonitor.EnergyViewMode;
import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
import com.lanternsoftware.datamodel.currentmonitor.archive.ArchiveStatus;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
import com.lanternsoftware.util.DateRange;
import com.lanternsoftware.util.dao.auth.AuthCode;
import com.lanternsoftware.util.dao.mongo.MongoProxy;
@ -44,7 +45,6 @@ public interface CurrentMonitorDao {
void putConfig(BreakerConfig _config);
void rebuildSummaries(int _accountId);
void rebuildSummariesAsync(int _accountId);
void rebuildSummaries(int _accountId, Date _start, Date _end);
String addPasswordResetKey(String _email);
@ -54,6 +54,7 @@ public interface CurrentMonitorDao {
String getAuthCodeForEmail(String _email, TimeZone _tz);
Account authCodeToAccount(String _authCode);
AuthCode decryptAuthCode(String _authCode);
String exchangeAuthCode(String _authCode, int _acctId);
Account putAccount(Account _account);
Account getAccount(int _accountId);
@ -65,5 +66,8 @@ public interface CurrentMonitorDao {
List<HubCommand> getAllHubCommands();
void deleteHubCommand(String _id);
void putHubSample(HubSample _sample);
List<HubSample> getSamplesForAccount(int _accountId);
MongoProxy getProxy();
}

View File

@ -21,6 +21,7 @@ import com.lanternsoftware.datamodel.currentmonitor.archive.ArchiveStatus;
import com.lanternsoftware.datamodel.currentmonitor.archive.BreakerEnergyArchive;
import com.lanternsoftware.datamodel.currentmonitor.archive.DailyEnergyArchive;
import com.lanternsoftware.datamodel.currentmonitor.archive.MonthlyEnergyArchive;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.DateRange;
import com.lanternsoftware.util.DateUtils;
@ -90,6 +91,9 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
updateEnergySummaries(minute);
}
proxy.delete(DirtyMinute.class, new DaoQuery());
if (!proxy.exists(Sequence.class, null)) {
proxy.save(new Sequence());
}
}
public void shutdown() {
@ -303,7 +307,7 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
TimeZone tz = getTimeZoneForAccount(_accountId);
Date month = DateUtils.getStartOfMonth(range.getStart(), tz);
Date end = DateUtils.getEndOfMonth(range.getEnd(), tz);
while (month.before(end)) {
while ((month != null) && month.before(end)) {
statuses.computeIfAbsent(month, _m->new ArchiveStatus(_accountId, _m, 0));
month = DateUtils.addMonths(month, 1, tz);
}
@ -326,6 +330,8 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
TimeZone tz = getTimeZoneForAccount(_minute.getAccountId());
BreakerConfig config = getConfig(_minute.getAccountId());
BreakerGroup group = CollectionUtils.getFirst(config.getBreakerGroups());
if (group == null)
return;
Date day = DateUtils.getMidnightBefore(_minute.getMinuteAsDate(), tz);
DebugTimer t2 = new DebugTimer("Updating energy", logger);
EnergySummary energy = getEnergySummary(_minute.getAccountId(), group.getId(), EnergyViewMode.DAY, day);
@ -412,13 +418,13 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
putEnergySummary(summary);
}
private void updateChargeSummary(BreakerConfig _config, EnergySummary _energySummary, TimeZone _tz) {
public void updateChargeSummary(BreakerConfig _config, EnergySummary _energySummary, TimeZone _tz) {
Date lookback = null;
for (BillingPlan p : CollectionUtils.makeNotNull(_config.getBillingPlans())) {
Date cycleStart = p.getBillingCycleStart(_energySummary.getStart(), _tz);
if (cycleStart.after(_energySummary.getStart()))
cycleStart = DateUtils.addMonths(cycleStart, -1, _tz);
if ((lookback == null) || cycleStart.before(lookback))
if ((lookback == null) || ((cycleStart != null) && cycleStart.before(lookback)))
lookback = cycleStart;
}
if (lookback != null) {
@ -462,7 +468,7 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
Date yearMonthStart = yearStart;
Set<String> monthSummaryIds = new HashSet<>();
Date loopEnd = DateUtils.addDays(yearEnd, 1, _tz);
while (yearMonthStart.before(loopEnd)) {
while ((yearMonthStart != null) && yearMonthStart.before(loopEnd)) {
Date billingStart = plan.getBillingCycleStart(yearMonthStart, _tz);
if (DateUtils.isBetween(billingStart, yearStart, yearEnd))
monthSummaryIds.add(ChargeSummary.toId(rootGroup.getAccountId(), plan.getPlanId(), rootGroup.getId(), EnergyViewMode.MONTH, billingStart));
@ -482,17 +488,13 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
}
}
@Override
public void rebuildSummariesAsync(int _accountId) {
executor.submit(() -> rebuildSummaries(_accountId));
}
@Override
public void rebuildSummaries(int _accountId) {
HubPowerMinute firstMinute = proxy.queryOne(HubPowerMinute.class, new DaoQuery("account_id", _accountId), DaoSort.sort("minute"));
if (firstMinute == null)
return;
rebuildSummaries(_accountId, firstMinute.getMinuteAsDate(), new Date());
HubPowerMinute lastMinute = proxy.queryOne(HubPowerMinute.class, new DaoQuery("account_id", _accountId), DaoSort.sortDesc("minute"));
rebuildSummaries(_accountId, firstMinute.getMinuteAsDate(), lastMinute.getMinuteAsDate());
}
@Override
@ -660,7 +662,7 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
if (NullUtils.isEmpty(_username) || NullUtils.isEmpty(_password))
return null;
Account acct = proxy.queryOne(Account.class, new DaoQuery("username", _username.toLowerCase().trim()));
if ((acct == null) || !BCrypt.checkpw(_password, acct.getPassword()))
if ((acct == null) || !BCrypt.checkpw(_password, NullUtils.makeNotNull(acct.getPassword())))
return null;
return toAuthCode(acct.getId(), acct.getAuxiliaryAccountIds());
}
@ -691,6 +693,11 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
return (account == null)?null:toAuthCode(account.getId(), account.getAuxiliaryAccountIds());
}
@Override
public String exchangeAuthCode(String _authCode, int _acctId) {
return null;
}
public String toAuthCode(int _acctId, List<Integer> _auxAcctIds) {
if (_acctId < 1)
return null;
@ -777,6 +784,8 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
if (entity == null)
return false;
Account acct = getAccountByUsername(aes.decryptFromBase64ToString(_key));
if (acct == null)
return false;
acct.setPassword(_password);
putAccount(acct);
proxy.delete("password_reset", new DaoQuery("_id", _key));
@ -800,6 +809,16 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
proxy.delete(HubCommand.class, new DaoQuery("_id", _id));
}
@Override
public void putHubSample(HubSample _sample) {
proxy.save(_sample);
}
@Override
public List<HubSample> getSamplesForAccount(int _accountId) {
return proxy.query(HubSample.class, new DaoQuery("account_id", _accountId));
}
@Override
public MongoProxy getProxy() {
return proxy;

View File

@ -1,21 +1,21 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>lantern-datamodel-currentmonitor</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<version>1.1.0</version>
<name>lantern-datamodel-currentmonitor</name>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>currentmonitor</artifactId>
<version>1.1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-dao</artifactId>
<version>1.0.0</version>
<version>${util.version}</version>
</dependency>
</dependencies>
<build>
@ -28,7 +28,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<version>3.10.1</version>
<executions>
<execution>
<goals>
@ -41,14 +41,14 @@
<optimize>true</optimize>
<showDeprecation>true</showDeprecation>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.4</version>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
@ -58,16 +58,6 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<index>true</index>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -21,11 +21,13 @@ public class Breaker implements IIdentical<Breaker> {
private String name;
private String description;
private int sizeAmps;
private int phaseOffsetNs;
private double calibrationFactor;
private double lowPassFilter;
private BreakerPolarity polarity;
private boolean doublePower;
private BreakerType type;
private boolean main;
private transient String key;
public Breaker() {
@ -139,6 +141,14 @@ public class Breaker implements IIdentical<Breaker> {
sizeAmps = _sizeAmps;
}
public int getPhaseOffsetNs() {
return phaseOffsetNs;
}
public void setPhaseOffsetNs(int _phaseOffsetNs) {
phaseOffsetNs = _phaseOffsetNs;
}
public double getLowPassFilter() {
return Math.abs(lowPassFilter) < 0.05 ? 1.6 : lowPassFilter;
}
@ -148,7 +158,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 +194,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 +287,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 && phaseOffsetNs == _o.phaseOffsetNs && 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,8 +203,20 @@ 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(billingRates, BillingRate::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

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

@ -11,6 +11,8 @@ public class BreakerHub implements IIdentical<BreakerHub> {
private int hub;
private double voltageCalibrationFactor;
private double portCalibrationFactor;
private int phaseCnt;
private int phaseOffsetNs;
private int frequency;
private String bluetoothMac;
@ -39,15 +41,31 @@ public class BreakerHub implements IIdentical<BreakerHub> {
}
public double getPortCalibrationFactor() {
return portCalibrationFactor == 0.0?1.25:portCalibrationFactor;
return portCalibrationFactor == 0.0?1.20:portCalibrationFactor;
}
public void setPortCalibrationFactor(double _portCalibrationFactor) {
portCalibrationFactor = _portCalibrationFactor;
}
public int getPhaseCnt() {
return phaseCnt == 0 ? 2 : phaseCnt;
}
public void setPhaseCnt(int _phaseCnt) {
phaseCnt = _phaseCnt;
}
public int getPhaseOffsetNs() {
return phaseOffsetNs;
}
public void setPhaseOffsetNs(int _phaseOffsetNs) {
phaseOffsetNs = _phaseOffsetNs;
}
public int getFrequency() {
return frequency;
return frequency == 0 ? 60 : frequency;
}
public void setFrequency(int _frequency) {
@ -73,7 +91,7 @@ public class BreakerHub implements IIdentical<BreakerHub> {
@Override
public boolean isIdentical(BreakerHub _o) {
if (this == _o) return true;
return hub == _o.hub && Double.compare(_o.voltageCalibrationFactor, voltageCalibrationFactor) == 0 && Double.compare(_o.portCalibrationFactor, portCalibrationFactor) == 0 && frequency == _o.frequency && Objects.equals(bluetoothMac, _o.bluetoothMac);
return hub == _o.hub && Double.compare(_o.voltageCalibrationFactor, voltageCalibrationFactor) == 0 && Double.compare(_o.portCalibrationFactor, portCalibrationFactor) == 0 && getPhaseCnt() == _o.getPhaseCnt() && getPhaseOffsetNs() == _o.getPhaseOffsetNs() && getFrequency() == _o.getFrequency() && Objects.equals(bluetoothMac, _o.bluetoothMac);
}
@Override

View File

@ -2,5 +2,7 @@ package com.lanternsoftware.datamodel.currentmonitor;
public enum BreakerPolarity {
NORMAL,
SOLAR;
SOLAR,
BI_DIRECTIONAL,
BI_DIRECTIONAL_INVERTED
}

View File

@ -0,0 +1,52 @@
package com.lanternsoftware.datamodel.currentmonitor;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
@DBSerializable
public class EmailCredentials {
private EmailProvider provider;
private String apiKey;
private String apiSecret;
private String emailFrom;
private String serverUrlBase;
public EmailProvider getProvider() {
return provider;
}
public void setProvider(EmailProvider _provider) {
provider = _provider;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String _apiKey) {
apiKey = _apiKey;
}
public String getApiSecret() {
return apiSecret;
}
public void setApiSecret(String _apiSecret) {
apiSecret = _apiSecret;
}
public String getEmailFrom() {
return emailFrom;
}
public void setEmailFrom(String _emailFrom) {
emailFrom = _emailFrom;
}
public String getServerUrlBase() {
return serverUrlBase;
}
public void setServerUrlBase(String _serverUrlBase) {
serverUrlBase = _serverUrlBase;
}
}

View File

@ -0,0 +1,6 @@
package com.lanternsoftware.datamodel.currentmonitor;
public enum EmailProvider {
SENDGRID,
MAILJET
}

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;
@ -136,9 +140,9 @@ public class EnergySummary {
idx = 0;
double flow = 0.0;
for (Float power : CollectionUtils.makeNotNull(breaker.getReadings())) {
if ((b.getPolarity() == BreakerPolarity.SOLAR) && (meter.flow[idx] < 0.0))
if (power < 0 && (meter.flow[idx] < 0.0))
flow -= meter.flow[idx] * (power / meter.solar[idx]);
else if ((b.getPolarity() != BreakerPolarity.SOLAR) && (meter.flow[idx] > 0.0))
else if (power > 0 && (meter.flow[idx] > 0.0))
flow += meter.flow[idx] * (power / meter.usage[idx]);
idx++;
}
@ -148,11 +152,11 @@ public class EnergySummary {
}
public void resetEnergy(Date _readTime) {
if (energy == null)
return;
int idx = viewMode.blockIndex(start, _readTime, timezone);
if (idx < energy.length)
energy[idx] = 0f;
if (energy != null) {
int idx = viewMode.blockIndex(start, _readTime, timezone);
if (idx < energy.length)
energy[idx] = 0f;
}
for (EnergySummary subGroup : CollectionUtils.makeNotNull(subGroups)) {
subGroup.resetEnergy(_readTime);
}
@ -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

@ -21,7 +21,7 @@ public enum HubConfigCharacteristic {
Shutdown(13, CharacteristicFlag.WRITE),
Version(14, CharacteristicFlag.READ),
Update(15, CharacteristicFlag.WRITE),
ReloadConfig(15, CharacteristicFlag.WRITE);
ReloadConfig(16, CharacteristicFlag.WRITE);
public final int idx;
public final UUID uuid;

View File

@ -8,7 +8,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.UUID;
public class HubConfigService {
public abstract class HubConfigService {
public static final UUIDFormatter uuidFormat = new UUIDFormatter("c5650001-d50f-49af-b906-cada0dc17937");
private static final AESTool aes = new AESTool(37320708309265127L,-8068168662055796771L,-4867793276337148572L,4425609941731230765L);
private static final UUID serviceUUID = uuidFormat.format(1);
@ -20,7 +20,7 @@ public class HubConfigService {
return serviceUUID;
}
public List<HubConfigCharacteristic> getCharacteristics() {
public static List<HubConfigCharacteristic> getCharacteristics() {
return Arrays.asList(HubConfigCharacteristic.values());
}

View File

@ -36,6 +36,10 @@ public class NetworkStatus {
pingSuccessful = _pingSuccessful;
}
public boolean isNetworkConnected() {
return isWifiConnected() || isEthernetConnected();
}
public boolean isWifiConnected() {
return CollectionUtils.isNotEmpty(wifiIPs);
}

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

@ -28,6 +28,8 @@ public class BreakerHubSerializer extends AbstractDaoSerializer<BreakerHub>
d.put("hub", _o.getHub());
d.put("voltage_calibration_factor", _o.getRawVoltageCalibrationFactor());
d.put("port_calibration_factor", _o.getRawPortCalibrationFactor());
d.put("phase_cnt", _o.getPhaseCnt());
d.put("phase_offset_ns", _o.getPhaseOffsetNs());
d.put("frequency", _o.getFrequency());
d.put("bluetooth_mac", _o.getBluetoothMac());
return d;
@ -40,6 +42,8 @@ public class BreakerHubSerializer extends AbstractDaoSerializer<BreakerHub>
o.setHub(DaoSerializer.getInteger(_d, "hub"));
o.setVoltageCalibrationFactor(DaoSerializer.getDouble(_d, "voltage_calibration_factor"));
o.setPortCalibrationFactor(DaoSerializer.getDouble(_d, "port_calibration_factor"));
o.setPhaseCnt(DaoSerializer.getInteger(_d, "phase_cnt"));
o.setPhaseOffsetNs(DaoSerializer.getInteger(_d, "phase_offset_ns"));
o.setFrequency(DaoSerializer.getInteger(_d, "frequency"));
o.setBluetoothMac(DaoSerializer.getString(_d, "bluetooth_mac"));
return o;

View File

@ -35,11 +35,13 @@ public class BreakerSerializer extends AbstractDaoSerializer<Breaker>
d.put("name", _o.getName());
d.put("description", _o.getDescription());
d.put("size_amps", _o.getSizeAmps());
d.put("phase_offset_ns", _o.getPhaseOffsetNs());
d.put("calibration_factor", _o.getCalibrationFactor());
d.put("low_pass_filter", _o.getLowPassFilter());
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;
}
@ -55,11 +57,13 @@ public class BreakerSerializer extends AbstractDaoSerializer<Breaker>
o.setName(DaoSerializer.getString(_d, "name"));
o.setDescription(DaoSerializer.getString(_d, "description"));
o.setSizeAmps(DaoSerializer.getInteger(_d, "size_amps"));
o.setPhaseOffsetNs(DaoSerializer.getInteger(_d, "phase_offset_ns"));
o.setCalibrationFactor(DaoSerializer.getDouble(_d, "calibration_factor"));
o.setLowPassFilter(DaoSerializer.getDouble(_d, "low_pass_filter"));
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

@ -0,0 +1,48 @@
package com.lanternsoftware.datamodel.currentmonitor.dao;
import com.lanternsoftware.datamodel.currentmonitor.EmailCredentials;
import com.lanternsoftware.datamodel.currentmonitor.EmailProvider;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class EmailCredentialsSerializer extends AbstractDaoSerializer<EmailCredentials>
{
@Override
public Class<EmailCredentials> getSupportedClass()
{
return EmailCredentials.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(EmailCredentials _o)
{
DaoEntity d = new DaoEntity();
d.put("provider", DaoSerializer.toEnumName(_o.getProvider()));
d.put("api_key", _o.getApiKey());
d.put("api_secret", _o.getApiSecret());
d.put("email_from", _o.getEmailFrom());
d.put("server_url_base", _o.getServerUrlBase());
return d;
}
@Override
public EmailCredentials fromDaoEntity(DaoEntity _d)
{
EmailCredentials o = new EmailCredentials();
o.setProvider(DaoSerializer.getEnum(_d, "provider", EmailProvider.class));
o.setApiKey(DaoSerializer.getString(_d, "api_key"));
o.setApiSecret(DaoSerializer.getString(_d, "api_secret"));
o.setEmailFrom(DaoSerializer.getString(_d, "email_from"));
o.setServerUrlBase(DaoSerializer.getString(_d, "server_url_base"));
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

@ -0,0 +1,41 @@
package com.lanternsoftware.datamodel.currentmonitor.hub;
import com.lanternsoftware.datamodel.currentmonitor.Breaker;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import java.util.List;
@DBSerializable
public class BreakerSample {
private int panel;
private int space;
private List<PowerSample> samples;
public int key() {
return Breaker.intKey(panel, space);
}
public int getPanel() {
return panel;
}
public void setPanel(int _panel) {
panel = _panel;
}
public int getSpace() {
return space;
}
public void setSpace(int _space) {
space = _space;
}
public List<PowerSample> getSamples() {
return samples;
}
public void setSamples(List<PowerSample> _samples) {
samples = _samples;
}
}

View File

@ -0,0 +1,42 @@
package com.lanternsoftware.datamodel.currentmonitor.hub;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import java.util.Date;
import java.util.List;
@DBSerializable(autogen = false)
public class HubSample {
private int accountId;
private Date sampleDate;
private List<BreakerSample> breakers;
public String getId() {
return String.format("%d-%d", accountId, DateUtils.toLong(sampleDate));
}
public int getAccountId() {
return accountId;
}
public void setAccountId(int _accountId) {
accountId = _accountId;
}
public Date getSampleDate() {
return sampleDate;
}
public void setSampleDate(Date _sampleDate) {
sampleDate = _sampleDate;
}
public List<BreakerSample> getBreakers() {
return breakers;
}
public void setBreakers(List<BreakerSample> _breakers) {
breakers = _breakers;
}
}

View File

@ -0,0 +1,43 @@
package com.lanternsoftware.datamodel.currentmonitor.hub;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
@DBSerializable
public class PowerSample {
public long nanoTime;
public int cycle;
public double voltage;
public double current;
public long getNanoTime() {
return nanoTime;
}
public void setNanoTime(long _nanoTime) {
nanoTime = _nanoTime;
}
public int getCycle() {
return cycle;
}
public void setCycle(int _cycle) {
cycle = _cycle;
}
public double getVoltage() {
return voltage;
}
public void setVoltage(double _voltage) {
voltage = _voltage;
}
public double getCurrent() {
return current;
}
public void setCurrent(double _current) {
current = _current;
}
}

View File

@ -0,0 +1,44 @@
package com.lanternsoftware.datamodel.currentmonitor.hub.dao;
import com.lanternsoftware.datamodel.currentmonitor.hub.BreakerSample;
import com.lanternsoftware.datamodel.currentmonitor.hub.PowerSample;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class BreakerSampleSerializer extends AbstractDaoSerializer<BreakerSample>
{
@Override
public Class<BreakerSample> getSupportedClass()
{
return BreakerSample.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(BreakerSample _o)
{
DaoEntity d = new DaoEntity();
d.put("panel", _o.getPanel());
d.put("space", _o.getSpace());
d.put("samples", DaoSerializer.toDaoEntities(_o.getSamples(), DaoProxyType.MONGO));
return d;
}
@Override
public BreakerSample fromDaoEntity(DaoEntity _d)
{
BreakerSample o = new BreakerSample();
o.setPanel(DaoSerializer.getInteger(_d, "panel"));
o.setSpace(DaoSerializer.getInteger(_d, "space"));
o.setSamples(DaoSerializer.getList(_d, "samples", PowerSample.class));
return o;
}
}

View File

@ -0,0 +1,45 @@
package com.lanternsoftware.datamodel.currentmonitor.hub.dao;
import com.lanternsoftware.datamodel.currentmonitor.hub.BreakerSample;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class HubSampleSerializer extends AbstractDaoSerializer<HubSample>
{
@Override
public Class<HubSample> getSupportedClass()
{
return HubSample.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(HubSample _o)
{
DaoEntity d = new DaoEntity();
d.put("_id", _o.getId());
d.put("account_id", _o.getAccountId());
d.put("sample_date", DaoSerializer.toLong(_o.getSampleDate()));
d.put("breakers", DaoSerializer.toDaoEntities(_o.getBreakers(), DaoProxyType.MONGO));
return d;
}
@Override
public HubSample fromDaoEntity(DaoEntity _d)
{
HubSample o = new HubSample();
o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
o.setSampleDate(DaoSerializer.getDate(_d, "sample_date"));
o.setBreakers(DaoSerializer.getList(_d, "breakers", BreakerSample.class));
return o;
}
}

View File

@ -0,0 +1,45 @@
package com.lanternsoftware.datamodel.currentmonitor.hub.dao;
import com.lanternsoftware.datamodel.currentmonitor.hub.PowerSample;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class PowerSampleSerializer extends AbstractDaoSerializer<PowerSample>
{
@Override
public Class<PowerSample> getSupportedClass()
{
return PowerSample.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(PowerSample _o)
{
DaoEntity d = new DaoEntity();
d.put("nano_time", _o.getNanoTime());
d.put("cycle", _o.getCycle());
d.put("voltage", _o.getVoltage());
d.put("current", _o.getCurrent());
return d;
}
@Override
public PowerSample fromDaoEntity(DaoEntity _d)
{
PowerSample o = new PowerSample();
o.setNanoTime(DaoSerializer.getLong(_d, "nano_time"));
o.setCycle(DaoSerializer.getInteger(_d, "cycle"));
o.setVoltage(DaoSerializer.getDouble(_d, "voltage"));
o.setCurrent(DaoSerializer.getDouble(_d, "current"));
return o;
}
}

View File

@ -15,6 +15,7 @@ com.lanternsoftware.datamodel.currentmonitor.dao.BreakerPowerSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.BreakerSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.ChargeSummarySerializer
com.lanternsoftware.datamodel.currentmonitor.dao.ChargeTotalSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.EmailCredentialsSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.EnergyBlockSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.EnergySummarySerializer
com.lanternsoftware.datamodel.currentmonitor.dao.EnergyTotalSerializer
@ -25,3 +26,6 @@ com.lanternsoftware.datamodel.currentmonitor.dao.MeterSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.NetworkStatusSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.SequenceSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.SignupResponseSerializer
com.lanternsoftware.datamodel.currentmonitor.hub.dao.BreakerSampleSerializer
com.lanternsoftware.datamodel.currentmonitor.hub.dao.HubSampleSerializer
com.lanternsoftware.datamodel.currentmonitor.hub.dao.PowerSampleSerializer

View File

@ -1,54 +1,41 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>lantern-service-currentmonitor</artifactId>
<packaging>war</packaging>
<version>1.0.0</version>
<version>1.1.0</version>
<name>lantern-service-currentmonitor</name>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client-bom</artifactId>
<version>1.32.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<parent>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>currentmonitor</artifactId>
<version>1.1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>lantern-dataaccess-currentmonitor</artifactId>
<version>1.0.0</version>
<version>${cm.version}</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-servlet</artifactId>
<version>1.0.0</version>
<version>${util.version}</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-cloudservices</artifactId>
<version>${util.version}</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-http</artifactId>
<version>1.0.0</version>
<scope>test</scope>
<version>${util.version}</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.rules</groupId>
<artifactId>lantern-service-rules</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>${rules.version}</version>
</dependency>
<dependency>
<groupId>javax</groupId>
@ -67,9 +54,14 @@
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.sendgrid</groupId>
<artifactId>sendgrid-java</artifactId>
<version>4.7.2</version>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.1</version>
</dependency>
<dependency>
<groupId>com.mailjet</groupId>
<artifactId>mailjet-client</artifactId>
<version>4.2.0</version>
</dependency>
</dependencies>
<build>
@ -82,7 +74,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<version>3.10.1</version>
<executions>
<execution>
<goals>
@ -95,13 +87,13 @@
<optimize>true</optimize>
<showDeprecation>true</showDeprecation>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.5</version>
<version>3.3.2</version>
<configuration>
<webResources>
<resource>

View File

@ -6,8 +6,9 @@ import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
import com.lanternsoftware.datamodel.currentmonitor.HubCommands;
import com.lanternsoftware.rules.RulesEngine;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.http.HttpFactory;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
@ -17,9 +18,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Globals implements ServletContextListener {
public static CurrentMonitorDao dao;
public static ExecutorService opsExecutor;
private static final Map<Integer, Map<Integer, List<HubCommand>>> commands = new HashMap<>();
@Override
@ -27,11 +31,14 @@ public class Globals implements ServletContextListener {
dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
RulesEngine.instance().start();
RulesEngine.instance().schedule(new CommandTask(), 0);
opsExecutor = Executors.newFixedThreadPool(7);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
opsExecutor.shutdown();
dao.shutdown();
HttpFactory.shutdown();
RulesEngine.shutdown();
}

View File

@ -0,0 +1,7 @@
package com.lanternsoftware.currentmonitor.email;
import com.lanternsoftware.datamodel.currentmonitor.EmailCredentials;
public interface IEmailProvider {
int sendTextEmail(EmailCredentials _credentials, String _to, String _subject, String _message);
}

View File

@ -0,0 +1,36 @@
package com.lanternsoftware.currentmonitor.email;
import com.lanternsoftware.datamodel.currentmonitor.EmailCredentials;
import com.mailjet.client.ClientOptions;
import com.mailjet.client.MailjetClient;
import com.mailjet.client.MailjetRequest;
import com.mailjet.client.MailjetResponse;
import com.mailjet.client.resource.Emailv31;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MailJetProvider implements IEmailProvider {
protected static final Logger LOG = LoggerFactory.getLogger(MailJetProvider.class);
@Override
public int sendTextEmail(EmailCredentials _credentials, String _to, String _subject, String _message) {
MailjetClient client;
MailjetRequest request;
MailjetResponse response;
client = new MailjetClient(_credentials.getApiKey(), _credentials.getApiSecret(), new ClientOptions("v3.1"));
request = new MailjetRequest(Emailv31.resource).property(Emailv31.MESSAGES, new JSONArray().put(new JSONObject()
.put(Emailv31.Message.FROM, new JSONObject().put("Email", _credentials.getEmailFrom()).put("Name", "Lantern Power Monitor"))
.put(Emailv31.Message.TO, new JSONArray().put(new JSONObject().put("Email", _to)))
.put(Emailv31.Message.SUBJECT, _subject)
.put(Emailv31.Message.TEXTPART, _message)));
try {
response = client.post(request);
return response.getStatus();
} catch (Exception _e) {
LOG.error("Failed to send email", _e);
return 500;
}
}
}

View File

@ -0,0 +1,26 @@
package com.lanternsoftware.currentmonitor.email;
import com.lanternsoftware.datamodel.currentmonitor.EmailCredentials;
public class SendGridProvider implements IEmailProvider {
@Override
public int sendTextEmail(EmailCredentials _credentials, String _to, String _subject, String _message) {
/* Email to = new Email(email);
Content content = new Content("text/plain", "Reset your password using this link:\nhttps://lanternpowermonitor.com/currentmonitor/resetPassword/" + key);
Mail mail = new Mail(from, subject, to, content);
SendGrid sg = new SendGrid(api_key);
Request request = new Request();
try {
request.setMethod(Method.POST);
request.setEndpoint("mail/send");
request.setBody(mail.build());
Response response = sg.api(request);
LOG.info("Password reset email status: {}\nfrom: {}\nto: {}\nkey: {}\nhost: {}", response.getStatusCode(), from.getEmail(), to.getEmail(), api_key, sg.getHost());
zipBsonResponse(_resp, new DaoEntity("success", response.getStatusCode() == 200));
} catch (IOException ex) {
LOG.error("Failed to send password reset email", ex);
_resp.setStatus(500);
}*/
return 500;
}
}

View File

@ -1,14 +1,15 @@
package com.lanternsoftware.currentmonitor.servlet;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.currentmonitor.util.GoogleAuthHelper;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.cloudservices.apple.AppleSSO;
import com.lanternsoftware.util.cloudservices.google.GoogleSSO;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.servlet.BasicAuth;
import com.lanternsoftware.util.servlet.LanternServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
@ -16,19 +17,27 @@ import javax.servlet.http.HttpServletResponse;
@WebServlet("/auth/*")
public class AuthServlet extends LanternServlet {
private static final Logger logger = LoggerFactory.getLogger(AuthServlet.class);
private static final GoogleSSO googleSSO = new GoogleSSO(LanternFiles.CONFIG_PATH + "google_sso.txt");
private static final AppleSSO appleSSO = new AppleSSO(LanternFiles.CONFIG_PATH + "apple_sso.txt");
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
String authCode = _req.getHeader("auth_code");
if (NullUtils.isEmpty(authCode)) {
String idToken = _req.getHeader("id_token");
String email = null;
if (NullUtils.isNotEmpty(idToken))
email = appleSSO.getEmailFromIdToken(idToken);
else if (NullUtils.isNotEmpty(authCode))
authCode = Globals.dao.exchangeAuthCode(authCode, DaoSerializer.toInteger(_req.getHeader("override_account")));
else {
BasicAuth auth = new BasicAuth(_req);
if (NullUtils.isEqual(auth.getUsername(), "googlesso")) {
logger.info("Attempting google SSO");
authCode = GoogleAuthHelper.signin(auth.getPassword(), DateUtils.fromTimeZoneId(_req.getHeader("timezone")));
} else
if (NullUtils.isEqual(auth.getUsername(), "googlesso"))
email = googleSSO.signin(auth.getPassword());
else
authCode = Globals.dao.authenticateAccount(auth.getUsername(), auth.getPassword());
}
if (NullUtils.isNotEmpty(email))
authCode = Globals.dao.getAuthCodeForEmail(email, DateUtils.fromTimeZoneId(_req.getHeader("timezone")));
DaoEntity rep = new DaoEntity("auth_code", authCode).and("timezone", Globals.dao.getTimeZoneForAccount(authCode));
if (isPath(_req, 0, "bin"))
zipBsonResponse(_rep, rep);

View File

@ -4,6 +4,7 @@ import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
import com.lanternsoftware.datamodel.currentmonitor.HubConfigCharacteristic;
import com.lanternsoftware.rules.RulesEngine;
import com.lanternsoftware.util.dao.auth.AuthCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -40,6 +41,8 @@ public class ConfigServlet extends SecureServiceServlet {
if ((oldConfig == null) || !oldConfig.isIdentical(config))
Globals.dao.putHubCommand(new HubCommand(config.getAccountId(), HubConfigCharacteristic.ReloadConfig, null));
Globals.dao.putConfig(config);
zipBsonResponse(_rep, Globals.dao.getMergedConfig(_authCode));
config = Globals.dao.getMergedConfig(_authCode);
RulesEngine.instance().sendFcmMessage(config.getAccountId(), config);
zipBsonResponse(_rep, config);
}
}

View File

@ -17,13 +17,14 @@ public class RebuildSummariesServlet extends SecureServiceServlet {
if (_authCode.getAccountId() == 100) {
String[] path = path(_req);
if (path.length > 0) {
Globals.dao.rebuildSummariesAsync(DaoSerializer.toInteger(CollectionUtils.get(path, 0)));
Globals.opsExecutor.submit(() -> Globals.dao.rebuildSummaries(DaoSerializer.toInteger(CollectionUtils.get(path, 0))));
}
else {
for (String sId : Globals.dao.getProxy().queryForField(Account.class, null, "_id")) {
int id = DaoSerializer.toInteger(sId);
if (id != 0)
Globals.dao.rebuildSummariesAsync(id);
if (id != 0) {
Globals.opsExecutor.submit(() -> Globals.dao.rebuildSummaries(id));
}
}
}
}

View File

@ -1,22 +1,19 @@
package com.lanternsoftware.currentmonitor.servlet;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.currentmonitor.email.IEmailProvider;
import com.lanternsoftware.currentmonitor.email.MailJetProvider;
import com.lanternsoftware.datamodel.currentmonitor.Account;
import com.lanternsoftware.datamodel.currentmonitor.EmailCredentials;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.email.EmailValidator;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.servlet.FreemarkerConfigUtil;
import com.lanternsoftware.util.servlet.FreemarkerServlet;
import com.sendgrid.Method;
import com.sendgrid.Request;
import com.sendgrid.Response;
import com.sendgrid.SendGrid;
import com.sendgrid.helpers.mail.Mail;
import com.sendgrid.helpers.mail.objects.Content;
import com.sendgrid.helpers.mail.objects.Email;
import freemarker.template.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -25,13 +22,13 @@ import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
@WebServlet("/resetPassword/*")
public class ResetPasswordServlet extends FreemarkerServlet {
protected static final Logger LOG = LoggerFactory.getLogger(ResetPasswordServlet.class);
protected static final Configuration CONFIG = FreemarkerConfigUtil.createConfig(ResetPasswordServlet.class, "/templates", 100);
protected static final String api_key = ResourceLoader.loadFileAsString(LanternFiles.CONFIG_PATH + "sendgrid.txt");
protected static final EmailCredentials credentials = DaoSerializer.parse(ResourceLoader.loadFileAsString(LanternFiles.CONFIG_PATH + "email.json"), EmailCredentials.class);
protected static final IEmailProvider provider = new MailJetProvider();
@Override
protected Configuration getFreemarkerConfig() {
@ -63,25 +60,11 @@ public class ResetPasswordServlet extends FreemarkerServlet {
} else {
DaoEntity payload = getRequestZipBson(_req);
String email = DaoSerializer.getString(payload, "email");
if (EmailValidator.getInstance().isValid(email)) {
Account account = Globals.dao.getAccountByUsername(email);
if ((account != null) && EmailValidator.getInstance().isValid(email)) {
String key = Globals.dao.addPasswordResetKey(email);
Email from = new Email("info@lanternsoftware.com");
String subject = "Password Reset - Lantern Power Monitor";
Email to = new Email(email);
Content content = new Content("text/plain", "Reset your password using this link:\nhttps://lanternsoftware.com/currentmonitor/resetPassword/" + key);
Mail mail = new Mail(from, subject, to, content);
SendGrid sg = new SendGrid(api_key);
Request request = new Request();
try {
request.setMethod(Method.POST);
request.setEndpoint("mail/send");
request.setBody(mail.build());
Response response = sg.api(request);
zipBsonResponse(_resp, new DaoEntity("success", response.getStatusCode() == 200));
} catch (IOException ex) {
LOG.error("Failed to send password reset email", ex);
_resp.setStatus(500);
}
int status = provider.sendTextEmail(credentials, email, "Password Reset - Lantern Power Monitor", "Reset your password using this link:\n" + credentials.getServerUrlBase() + "resetPassword/" + key);
zipBsonResponse(_resp, new DaoEntity("success", status == 200));
}
else
_resp.setStatus(400);

View File

@ -0,0 +1,21 @@
package com.lanternsoftware.currentmonitor.servlet;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
import com.lanternsoftware.util.dao.auth.AuthCode;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/sample")
public class SampleServlet extends SecureServiceServlet {
@Override
protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
HubSample sample = getRequestPayload(_req, HubSample.class);
if (sample == null)
return;
sample.setAccountId(_authCode.getAccountId());
Globals.dao.putHubSample(sample);
}
}

View File

@ -14,26 +14,27 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
@WebServlet("/signup")
@WebServlet("/signup/*")
public class SignupServlet extends LanternServlet {
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
boolean binary = isPath(_req, 0, "bin");
BasicAuth auth = new BasicAuth(_req);
Account acct = Globals.dao.getAccountByUsername(auth.getUsername().toLowerCase().trim());
if (acct != null) {
jsonResponse(_rep, SignupResponse.error("An account for " + auth.getUsername() + " already exists"));
jsonResponse(_rep, SignupResponse.error("An account for " + auth.getUsername() + " already exists"), binary);
return;
}
if (!EmailValidator.getInstance().isValid(auth.getUsername())) {
jsonResponse(_rep, SignupResponse.error(auth.getUsername() + " is not a valid email address"));
jsonResponse(_rep, SignupResponse.error(auth.getUsername() + " is not a valid email address"), binary);
return;
}
if (NullUtils.length(auth.getPassword()) < 8) {
jsonResponse(_rep, SignupResponse.error("Your password must be at least 8 characters long"));
jsonResponse(_rep, SignupResponse.error("Your password must be at least 8 characters long"), binary);
return;
}
if (NullUtils.isEqual("password", auth.getPassword())) {
jsonResponse(_rep, SignupResponse.error("Seriously? \"password\"? Come on."));
jsonResponse(_rep, SignupResponse.error("Seriously? \"password\"? Come on."), binary);
return;
}
acct = new Account();
@ -42,6 +43,6 @@ public class SignupServlet extends LanternServlet {
acct.setTimezone(DateUtils.fromTimeZoneId(_req.getHeader("timezone")).getID());
Globals.dao.putAccount(acct);
String authCode = Globals.dao.authenticateAccount(auth.getUsername(), auth.getPassword());
jsonResponse(_rep, SignupResponse.success(authCode, acct.getTimezone()));
jsonResponse(_rep, SignupResponse.success(authCode, acct.getTimezone()), binary);
}
}

View File

@ -0,0 +1,56 @@
package com.lanternsoftware.currentmonitor.servlet.console;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.auth.AuthCode;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public abstract class AuthenticatedConsoleServlet extends SecureConsoleServlet {
@Override
protected void get(HttpServletRequest _req, HttpServletResponse _rep) {
AuthCode code = getAuthCode(_req, _rep);
if (code != null)
get(code, _req, _rep);
}
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
}
@Override
protected void post(HttpServletRequest _req, HttpServletResponse _rep) {
AuthCode code = getAuthCode(_req, _rep);
if (code != null)
post(code, _req, _rep);
}
private AuthCode getAuthCode(HttpServletRequest _req, HttpServletResponse _rep) {
String sRequestURL = _req.getRequestURL().toString();
String sURL = sRequestURL.replaceFirst("http://", "https://");
if (!sURL.equals(sRequestURL)) {
String sQuery = _req.getQueryString();
if (NullUtils.isNotEmpty(sQuery))
sURL += "?" + sQuery;
redirect(_rep, sURL);
return null;
}
AuthCode authCode = Globals.dao.decryptAuthCode(DaoSerializer.toString(_req.getSession().getAttribute("auth_code")));
if (authCode == null) {
Cookie authCookie = CollectionUtils.filterOne(CollectionUtils.asArrayList(_req.getCookies()), _c-> NullUtils.isEqual(_c.getName(), "auth_code"));
if (authCookie != null)
authCode = Globals.dao.decryptAuthCode(authCookie.getValue());
}
if (authCode == null) {
redirect(_rep, _req.getContextPath() + "/login");
return null;
}
return authCode;
}
protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
}
}

View File

@ -9,7 +9,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("")
public class ConsoleServlet extends SecureConsoleServlet {
public class ConsoleServlet extends AuthenticatedConsoleServlet {
private static final Logger logger = LoggerFactory.getLogger(ConsoleServlet.class);
@Override

View File

@ -40,7 +40,7 @@ import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
@WebServlet("/export/*")
public class ExportServlet extends SecureConsoleServlet {
public class ExportServlet extends AuthenticatedConsoleServlet {
private static final Logger logger = LoggerFactory.getLogger(ExportServlet.class);
@Override

View File

@ -1,8 +1,10 @@
package com.lanternsoftware.currentmonitor.servlet.console;
import com.lanternsoftware.currentmonitor.servlet.FreemarkerCMServlet;
import com.lanternsoftware.currentmonitor.util.GoogleAuthHelper;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.cloudservices.google.GoogleSSO;
import com.lanternsoftware.util.external.LanternFiles;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
@ -10,23 +12,28 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/gso")
public class GsoServlet extends FreemarkerCMServlet {
public class GsoServlet extends SecureConsoleServlet {
private static final GoogleSSO googleSSO = new GoogleSSO(LanternFiles.CONFIG_PATH + "google_sso.txt");
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
protected void get(HttpServletRequest _req, HttpServletResponse _rep) {
render(_rep, "login.ftl", model(_req));
}
@Override
protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) {
protected void post(HttpServletRequest _req, HttpServletResponse _rep) {
String code = getRequestPayloadAsString(_req);
if (NullUtils.isNotEmpty(code)) {
String authCode = GoogleAuthHelper.signin(code, null);
if (NullUtils.isNotEmpty(authCode)) {
Cookie authCookie = new Cookie("auth_code", authCode);
authCookie.setMaxAge(157680000);
authCookie.setSecure(true);
_rep.addCookie(authCookie);
_req.getSession().setAttribute("auth_code", authCode);
String email = googleSSO.signin(code);
if (NullUtils.isNotEmpty(email)) {
String authCode = Globals.dao.getAuthCodeForEmail(email, DateUtils.fromTimeZoneId(_req.getHeader("timezone")));
if (NullUtils.isNotEmpty(authCode)) {
Cookie authCookie = new Cookie("auth_code", authCode);
authCookie.setMaxAge(157680000);
authCookie.setSecure(true);
_rep.addCookie(authCookie);
_req.getSession().setAttribute("auth_code", authCode);
}
}
}
}

View File

@ -1,31 +1,22 @@
package com.lanternsoftware.currentmonitor.servlet.console;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.currentmonitor.servlet.FreemarkerCMServlet;
import com.lanternsoftware.currentmonitor.util.GoogleAuthHelper;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.auth.AuthCode;
import com.lanternsoftware.util.servlet.LanternServlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends FreemarkerCMServlet {
public class LoginServlet extends SecureConsoleServlet {
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
protected void get(HttpServletRequest _req, HttpServletResponse _rep) {
render(_rep, "login.ftl", model(_req));
}
@Override
protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) {
protected void post(HttpServletRequest _req, HttpServletResponse _rep) {
String username = _req.getParameter("username");
String password = _req.getParameter("password");
String authCode = Globals.dao.authenticateAccount(username, password);

View File

@ -1,18 +1,14 @@
package com.lanternsoftware.currentmonitor.servlet.console;
import com.lanternsoftware.currentmonitor.servlet.FreemarkerCMServlet;
import com.lanternsoftware.currentmonitor.util.GoogleAuthHelper;
import com.lanternsoftware.util.NullUtils;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/logout")
public class LogoutServlet extends FreemarkerCMServlet {
public class LogoutServlet extends AuthenticatedConsoleServlet {
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
protected void get(HttpServletRequest _req, HttpServletResponse _rep) {
_req.getSession().removeAttribute("auth_code");
Cookie authCookie = new Cookie("auth_code", "");
authCookie.setMaxAge(0);
@ -22,6 +18,6 @@ public class LogoutServlet extends FreemarkerCMServlet {
}
@Override
protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) {
protected void post(HttpServletRequest _req, HttpServletResponse _rep) {
}
}

View File

@ -1,48 +1,40 @@
package com.lanternsoftware.currentmonitor.servlet.console;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.currentmonitor.servlet.FreemarkerCMServlet;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.auth.AuthCode;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public abstract class SecureConsoleServlet extends FreemarkerCMServlet {
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
AuthCode code = getAuthCode(_req, _rep);
if (code != null)
get(code, _req, _rep);
if (isSecure(_req, _rep))
get(_req, _rep);
}
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
protected void get(HttpServletRequest _req, HttpServletResponse _rep) {
}
@Override
protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) {
AuthCode code = getAuthCode(_req, _rep);
if (code != null)
post(code, _req, _rep);
if (isSecure(_req, _rep))
post(_req, _rep);
}
private AuthCode getAuthCode(HttpServletRequest _req, HttpServletResponse _rep) {
AuthCode authCode = Globals.dao.decryptAuthCode(DaoSerializer.toString(_req.getSession().getAttribute("auth_code")));
if (authCode == null) {
Cookie authCookie = CollectionUtils.filterOne(CollectionUtils.asArrayList(_req.getCookies()), _c-> NullUtils.isEqual(_c.getName(), "auth_code"));
if (authCookie != null)
authCode = Globals.dao.decryptAuthCode(authCookie.getValue());
}
if (authCode == null) {
redirect(_rep, _req.getContextPath() + "/login");
return null;
}
return authCode;
protected void post(HttpServletRequest _req, HttpServletResponse _rep) {
}
protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
private boolean isSecure(HttpServletRequest _req, HttpServletResponse _rep) {
String sRequestURL = _req.getRequestURL().toString();
String sURL = sRequestURL.replaceFirst("http://", "https://");
if (!sURL.equals(sRequestURL)) {
String sQuery = _req.getQueryString();
if (NullUtils.isNotEmpty(sQuery))
sURL += "?" + sQuery;
redirect(_rep, sURL);
return false;
}
return true;
}
}

View File

@ -1,42 +0,0 @@
package com.lanternsoftware.currentmonitor.util;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.TimeZone;
public class GoogleAuthHelper {
private static final Logger logger = LoggerFactory.getLogger(GoogleAuthHelper.class);
private static final NetHttpTransport transport = new NetHttpTransport();
private static final String googleClientId;
private static final String googleClientSecret;
static {
DaoEntity google = DaoSerializer.parse(ResourceLoader.loadFileAsString(LanternFiles.CONFIG_PATH + "google_sso.txt"));
googleClientId = DaoSerializer.getString(google, "id");
googleClientSecret = DaoSerializer.getString(google, "secret");
}
public static String signin(String _code, TimeZone _tz) {
try {
GoogleTokenResponse tokenResponse = new GoogleAuthorizationCodeTokenRequest(transport, new GsonFactory(), "https://oauth2.googleapis.com/token", googleClientId, googleClientSecret, _code, "postmessage").execute();
if (tokenResponse != null) {
GoogleIdToken idToken = tokenResponse.parseIdToken();
if (idToken != null)
return Globals.dao.getAuthCodeForEmail(idToken.getPayload().getEmail(), _tz);
}
} catch (Exception _e) {
logger.error("Failed to validate google auth code", _e);
}
return null;
}
}

View File

@ -3,7 +3,7 @@
<property name="log.pattern" value="%date %-5level %logger{0} - %message%n"/>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/opt/tomcat/logs/log.txt</file>
<file>/opt/tomcat/log/log.txt</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>/opt/tomcat/log/log.%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
<maxFileSize>20MB</maxFileSize>

View File

@ -1,10 +0,0 @@
package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
public class CreateMongoConfig {
public static void main(String[] args) {
new MongoConfig("lanternsoftware.com", "*redacted*", "*redacted*", "CURRENT_MONITOR").saveToDisk(LanternFiles.CONFIG_PATH + "mongo.cfg");
}
}

View File

@ -0,0 +1,37 @@
package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
import com.lanternsoftware.datamodel.currentmonitor.EnergySummary;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.http.HttpPool;
import com.lanternsoftware.util.servlet.BasicAuth;
import org.apache.http.client.methods.HttpGet;
import java.util.Date;
import java.util.TimeZone;
public class GetEnergySummary {
public static void main(String[] args) {
HttpPool pool = new HttpPool(10, 10, 10000, 10000, 10000);
HttpGet authRequest = new HttpGet("https://lanternpowermonitor.com/currentmonitor/auth");
authRequest.addHeader("Authorization", BasicAuth.toHeader("<username>", "<password>"));
String authRep = pool.executeToString(authRequest);
String authCode = DaoSerializer.getString(DaoSerializer.parse(authRep), "auth_code");
HttpGet configRequest = new HttpGet("https://lanternpowermonitor.com/currentmonitor/config");
configRequest.addHeader("auth_code", authCode);
String configRep = pool.executeToString(configRequest);
BreakerConfig config = DaoSerializer.parse(configRep, BreakerConfig.class);
Date day = DateUtils.date(6, 5, 2022, TimeZone.getTimeZone("America/Chicago"));
HttpGet summaryRequest = new HttpGet("https://lanternpowermonitor.com/currentmonitor/energy/" + config.getRootGroup().getId() + "/DAY/" + day.getTime());
summaryRequest.addHeader("auth_code", authCode);
EnergySummary summary = DaoSerializer.fromZipBson(pool.executeToByteArray(summaryRequest), EnergySummary.class);
System.out.println(DaoSerializer.toJson(summary));
pool.shutdown();
}
}

View File

@ -4,14 +4,16 @@
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>currentmonitor</artifactId>
<name>currentmonitor</name>
<version>1.0.0</version>
<version>1.1.0</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>com.lanternsoftware</groupId>
<artifactId>LanternPowerMonitor</artifactId>
<version>1.0.0</version>
</parent>
<modules>
<module>lantern-config-currentmonitor</module>
<module>lantern-currentmonitor</module>
<module>lantern-dataaccess-currentmonitor</module>
<module>lantern-datamodel-currentmonitor</module>

File diff suppressed because one or more lines are too long

View File

@ -1,25 +1,24 @@
G04 Layer: BoardOutline*
G04 EasyEDA v6.4.0, 2020-07-14T23:16:01--5:00*
G04 6dc5d916f8a9456ea10d5ff2c879efc5,9df6f537d2f94b3ba0ed850023b5714d,10*
G04 EasyEDA v6.4.25, 2022-04-08T13:20:52--5:00*
G04 Gerber Generator version 0.2*
G04 Scale: 100 percent, Rotated: No, Reflected: No *
G04 Dimensions in millimeters *
G04 leading zeros omitted , absolute positions ,3 integer and 3 decimal *
%FSLAX33Y33*%
G04 leading zeros omitted , absolute positions ,4 integer and 5 decimal *
%FSLAX45Y45*%
%MOMM*%
G90*
G71D02*
%ADD10C,0.254000*%
G54D10*
G01X0Y56000D02*
G01X64999Y56000D01*
G01X64999Y0D01*
G01X64999Y0D02*
G01X0Y0D01*
G01X0Y0D02*
G01X0Y56000D01*
%ADD10C,0.2540*%
D10*
X0Y0D02*
G01*
X6499994Y0D01*
X6499994Y-5600006D01*
X6499994Y-5600006D02*
G01*
X0Y-5600006D01*
X0Y-5600006D02*
G01*
X0Y0D01*
%LPD*%
M00*
M02*

View File

@ -1,382 +1,568 @@
G04 Layer: BottomLayer*
G04 EasyEDA v6.4.0, 2020-07-14T23:16:01--5:00*
G04 6dc5d916f8a9456ea10d5ff2c879efc5,9df6f537d2f94b3ba0ed850023b5714d,10*
G04 EasyEDA v6.4.25, 2022-04-08T13:20:52--5:00*
G04 Gerber Generator version 0.2*
G04 Scale: 100 percent, Rotated: No, Reflected: No *
G04 Dimensions in millimeters *
G04 leading zeros omitted , absolute positions ,3 integer and 3 decimal *
%FSLAX33Y33*%
G04 leading zeros omitted , absolute positions ,4 integer and 5 decimal *
%FSLAX45Y45*%
%MOMM*%
G90*
G71D02*
%ADD10C,0.254000*%
%ADD12C,0.609600*%
%ADD13R,1.799996X1.799996*%
%ADD14C,1.799996*%
%ADD15C,1.199998*%
%ADD16C,1.524000*%
%ADD17C,1.599997*%
%ADD10C,0.2540*%
%ADD12C,0.6096*%
%ADD13R,1.8000X1.8000*%
%ADD14C,1.2000*%
%ADD15C,1.5240*%
%ADD16C,1.8000*%
%ADD17C,1.6000*%
%LPD*%
G54D10*
G01X8369Y53769D02*
G01X9639Y52499D01*
G01X11674Y52500D01*
G01X12174Y52000D01*
G01X12174Y48875D01*
G01X12999Y48141D01*
G01X12999Y47031D01*
G01X59499Y21125D02*
G01X59499Y21250D01*
G01X58750Y21999D01*
G01X51501Y21999D01*
G01X50250Y23250D01*
G01X50250Y34000D01*
G01X46979Y37270D01*
G01X44310Y37270D01*
G01X59500Y12124D02*
G01X59500Y12250D01*
G01X58000Y13750D01*
G01X51499Y13750D01*
G01X48749Y16500D01*
G01X48749Y32500D01*
G01X46519Y34730D01*
G01X44310Y34730D01*
G01X38874Y5499D02*
G01X38874Y6124D01*
G01X46250Y13500D01*
G01X46250Y28750D01*
G01X45349Y29650D01*
G01X44310Y29650D01*
G01X47873Y5499D02*
G01X47500Y5499D01*
G01X45999Y7000D01*
G01X45999Y10749D01*
G01X47500Y12251D01*
G01X47500Y30749D01*
G01X46060Y32190D01*
G01X44310Y32190D01*
G01X59499Y30125D02*
G01X59499Y30249D01*
G01X57749Y31999D01*
G01X55250Y31999D01*
G01X47439Y39810D01*
G01X44310Y39810D01*
G01X59500Y39124D02*
G01X59500Y39250D01*
G01X57749Y41000D01*
G01X53750Y41000D01*
G01X52399Y42350D01*
G01X44310Y42350D01*
G01X59500Y48124D02*
G01X59500Y48250D01*
G01X58500Y49250D01*
G01X53750Y49250D01*
G01X49390Y44890D01*
G01X44310Y44890D01*
G01X33769Y51229D02*
G01X33779Y49700D01*
G01X30500Y46600D01*
G01X30500Y38452D01*
G01X31219Y39820D02*
G01X31219Y33500D01*
G01X29879Y32180D01*
G01X28310Y32190D01*
G01X36690Y39810D02*
G01X36680Y39820D01*
G01X31219Y39820D01*
G01X36309Y53769D02*
G01X37579Y52520D01*
G01X37579Y49400D01*
G01X39999Y47000D01*
G01X39999Y43500D01*
G01X38849Y42350D01*
G01X36690Y42350D01*
G01X30500Y38453D02*
G01X30499Y36680D01*
G01X28310Y34730D01*
G01X28310Y34730D01*
G01X31999Y38450D02*
G01X33179Y37270D01*
G01X36689Y37270D01*
G01X12999Y28310D02*
G01X12999Y25549D01*
G01X24349Y14200D01*
G01X12999Y36729D02*
G01X12999Y28308D01*
G01X36689Y34730D02*
G01X33030Y34730D01*
G01X33000Y34700D01*
G01X15450Y13381D02*
G01X23530Y13381D01*
G01X24350Y14199D01*
G54D13*
G01X20690Y44890D03*
G01X20690Y42350D03*
G01X20690Y34730D03*
G01X20690Y32190D03*
G01X20690Y39810D03*
G01X20690Y37270D03*
G01X20690Y29650D03*
G01X20690Y27110D03*
G01X28310Y27110D03*
G01X28310Y29650D03*
G01X28310Y32190D03*
G01X28310Y34730D03*
G01X28310Y37270D03*
G01X28310Y39810D03*
G01X28310Y42350D03*
G01X28310Y44890D03*
G01X44310Y27110D03*
G01X44310Y29650D03*
G01X44310Y37270D03*
G01X44310Y39810D03*
G01X44310Y32190D03*
G01X44310Y34730D03*
G01X44310Y42350D03*
G01X44310Y44890D03*
G01X36690Y44890D03*
G01X36690Y42350D03*
G01X36690Y39810D03*
G01X36690Y37270D03*
G01X36690Y34730D03*
G01X36690Y32190D03*
G01X36690Y29650D03*
G01X36690Y27110D03*
D10*
X1545081Y-4261865D02*
G01*
X2353056Y-4261865D01*
X2435097Y-4180077D01*
X3668999Y-2127001D02*
G01*
X3303000Y-2127001D01*
X3300001Y-2130000D01*
X1299972Y-1927097D02*
G01*
X1299972Y-2769107D01*
X1299997Y-2768983D02*
G01*
X1299997Y-3045007D01*
X2434991Y-4180001D01*
X3199993Y-1754997D02*
G01*
X3317996Y-1873001D01*
X3668999Y-1873001D01*
X3050031Y-1754631D02*
G01*
X3049993Y-1931995D01*
X2831000Y-2127001D01*
X2831084Y-2126995D01*
X3630929Y-223012D02*
G01*
X3757993Y-348000D01*
X3757993Y-659998D01*
X3999991Y-899921D01*
X3999991Y-1249934D01*
X3884929Y-1364995D01*
X3669029Y-1364995D01*
X3669035Y-1618995D02*
G01*
X3668044Y-1618005D01*
X3121990Y-1618005D01*
X3121913Y-1617979D02*
G01*
X3121992Y-2249995D01*
X2987995Y-2381994D01*
X2831084Y-2380995D01*
X3376929Y-477012D02*
G01*
X3377994Y-629998D01*
X3050001Y-940000D01*
X3050001Y-1754728D01*
X5950000Y-787527D02*
G01*
X5950000Y-774999D01*
X5850000Y-674999D01*
X5375000Y-674999D01*
X4939004Y-1110995D01*
X4431004Y-1110995D01*
X5950000Y-1687525D02*
G01*
X5950000Y-1675000D01*
X5774999Y-1499999D01*
X5375000Y-1499999D01*
X5239997Y-1364995D01*
X4431004Y-1364995D01*
X5949950Y-2587497D02*
G01*
X5949950Y-2575052D01*
X5774943Y-2400045D01*
X5525008Y-2400045D01*
X4743958Y-1618995D01*
X4431029Y-1618995D01*
X4787391Y-5050027D02*
G01*
X4750054Y-5050027D01*
X4599940Y-4899913D01*
X4599940Y-4525009D01*
X4750054Y-4374895D01*
X4750054Y-2525013D01*
X4606036Y-2380995D01*
X4431029Y-2380995D01*
X3887470Y-5050027D02*
G01*
X3887470Y-4987543D01*
X4625086Y-4249927D01*
X4625086Y-2724912D01*
X4534915Y-2634995D01*
X4431029Y-2634995D01*
X5950000Y-4387517D02*
G01*
X5950000Y-4375000D01*
X5800001Y-4225000D01*
X5149999Y-4225000D01*
X4874999Y-3949999D01*
X4874999Y-2350000D01*
X4651994Y-2126995D01*
X4431004Y-2126995D01*
X5949950Y-3487420D02*
G01*
X5949950Y-3474974D01*
X5875020Y-3400043D01*
X5150104Y-3400043D01*
X5025001Y-3274999D01*
X5025001Y-2200000D01*
X4697984Y-1872995D01*
X4431029Y-1872995D01*
X836929Y-223012D02*
G01*
X963929Y-350012D01*
X1167498Y-349999D01*
X1217498Y-399999D01*
X1217498Y-712497D01*
X1299972Y-785876D01*
X1299972Y-896873D01*
D13*
G01*
X2069007Y-1110995D03*
G01*
X2069007Y-1364995D03*
G01*
X2069007Y-2126995D03*
G01*
X2069007Y-2380995D03*
G01*
X2069007Y-1618995D03*
G01*
X2069007Y-1872995D03*
G01*
X2069007Y-2634995D03*
G01*
X2069007Y-2888995D03*
G01*
X2831007Y-2888995D03*
G01*
X2831007Y-2634995D03*
G01*
X2831007Y-2380995D03*
G01*
X2831007Y-2126995D03*
G01*
X2831007Y-1872995D03*
G01*
X2831007Y-1618995D03*
G01*
X2831007Y-1364995D03*
G01*
X2831007Y-1110995D03*
G01*
X4431004Y-2888995D03*
G01*
X4431004Y-2634995D03*
G01*
X4431004Y-1872995D03*
G01*
X4431004Y-1618995D03*
G01*
X4431004Y-2380995D03*
G01*
X4431004Y-2126995D03*
G01*
X4431004Y-1364995D03*
G01*
X4431004Y-1110995D03*
G01*
X3669004Y-1110995D03*
G01*
X3669004Y-1364995D03*
G01*
X3669004Y-1618995D03*
G01*
X3669004Y-1872995D03*
G01*
X3669004Y-2126995D03*
G01*
X3669004Y-2380995D03*
G01*
X3669004Y-2634995D03*
G01*
X3669004Y-2888995D03*
D14*
G01*
X4999939Y-3699992D03*
G01*
X5150053Y-3699992D03*
D15*
G01*
X1299997Y-1926996D03*
G01*
X1299997Y-896975D03*
G01*
X5253990Y-2861005D03*
G01*
X5253990Y-1830984D03*
G01*
X4224985Y-3210001D03*
G01*
X5255006Y-3210001D03*
G01*
X1299997Y-3799001D03*
G01*
X1299997Y-2768980D03*
D14*
G01*
X1394942Y-4261993D03*
G01*
X1545056Y-4261993D03*
D15*
G01*
X3960012Y-4345025D03*
G01*
X3960012Y-3314979D03*
G01*
X2434996Y-4180001D03*
G01*
X3465017Y-4180001D03*
G36*
G01X57529Y52130D02*
G01X55729Y52130D01*
G01X55729Y50330D01*
G01X57529Y50330D01*
G01X57529Y52130D01*
X5752998Y-386999D02*
G01*
X5572998Y-386999D01*
X5572998Y-566999D01*
X5752998Y-566999D01*
G37*
G54D14*
G01X56630Y53769D03*
G01X54090Y51229D03*
G01X54090Y53769D03*
G01X51550Y51229D03*
G01X51550Y53769D03*
G01X49010Y51229D03*
G01X49010Y53769D03*
G01X46470Y51229D03*
G01X46470Y53769D03*
G01X43930Y51229D03*
G01X43930Y53769D03*
G01X41390Y51229D03*
G01X41390Y53769D03*
G01X38850Y51229D03*
G01X38850Y53769D03*
G01X36310Y51229D03*
G01X36310Y53769D03*
G01X33770Y51229D03*
G01X33770Y53769D03*
G01X31230Y51229D03*
G01X31230Y53769D03*
G01X28690Y51229D03*
G01X28690Y53769D03*
G01X26150Y51229D03*
G01X26150Y53769D03*
G01X23610Y51229D03*
G01X23610Y53769D03*
G01X21070Y51229D03*
G01X21070Y53769D03*
G01X18530Y51229D03*
G01X18530Y53769D03*
G01X15990Y51229D03*
G01X15990Y53769D03*
G01X13450Y51229D03*
G01X13450Y53769D03*
G01X10910Y51229D03*
G01X10910Y53769D03*
G01X8370Y51229D03*
G01X8370Y53769D03*
G54D15*
G01X49999Y19000D03*
G01X51500Y19000D03*
G54D16*
G01X12999Y36730D03*
G01X12999Y47030D03*
G01X52539Y27390D03*
G01X52539Y37690D03*
G01X42249Y23900D03*
G01X52550Y23900D03*
G01X12999Y18010D03*
G01X12999Y28310D03*
G54D15*
G01X13949Y13380D03*
G01X15450Y13380D03*
G54D16*
G01X39600Y12549D03*
G01X39600Y22850D03*
G01X24349Y14200D03*
G01X34650Y14200D03*
G54D12*
G01X31219Y39820D03*
G01X32999Y34700D03*
G01X31999Y38450D03*
G01X30500Y38452D03*
G01X36699Y25000D03*
G54D17*
G01X32050Y22424D02*
G01X32950Y22424D01*
G01X28600Y21225D02*
G01X28600Y22125D01*
G01X30000Y18624D02*
G01X30000Y17724D01*
G01X35000Y18624D02*
G01X35000Y17724D01*
G01X36399Y22125D02*
G01X36399Y21225D01*
G01X5050Y48124D02*
G01X5950Y48124D01*
G01X1600Y46924D02*
G01X1600Y47824D01*
G01X3000Y44324D02*
G01X3000Y43424D01*
G01X8000Y44324D02*
G01X8000Y43424D01*
G01X9400Y47824D02*
G01X9400Y46924D01*
G01X5050Y39124D02*
G01X5950Y39124D01*
G01X1600Y37924D02*
G01X1600Y38824D01*
G01X3000Y35324D02*
G01X3000Y34424D01*
G01X8000Y35324D02*
G01X8000Y34424D01*
G01X9400Y38824D02*
G01X9400Y37924D01*
G01X5050Y30124D02*
G01X5950Y30124D01*
G01X1600Y28925D02*
G01X1600Y29825D01*
G01X3000Y26324D02*
G01X3000Y25424D01*
G01X8000Y26324D02*
G01X8000Y25424D01*
G01X9400Y29825D02*
G01X9400Y28925D01*
G01X5050Y21124D02*
G01X5950Y21124D01*
G01X1600Y19925D02*
G01X1600Y20825D01*
G01X3000Y17324D02*
G01X3000Y16424D01*
G01X8000Y17324D02*
G01X8000Y16424D01*
G01X9400Y20825D02*
G01X9400Y19925D01*
G01X5050Y12124D02*
G01X5950Y12124D01*
G01X1600Y10925D02*
G01X1600Y11825D01*
G01X3000Y8324D02*
G01X3000Y7424D01*
G01X8000Y8324D02*
G01X8000Y7424D01*
G01X9400Y11825D02*
G01X9400Y10925D01*
G01X59050Y48124D02*
G01X59950Y48124D01*
G01X55600Y46924D02*
G01X55600Y47824D01*
G01X57000Y44324D02*
G01X57000Y43424D01*
G01X62000Y44324D02*
G01X62000Y43424D01*
G01X63399Y47824D02*
G01X63399Y46924D01*
G01X59050Y39124D02*
G01X59950Y39124D01*
G01X55600Y37924D02*
G01X55600Y38824D01*
G01X57000Y35324D02*
G01X57000Y34424D01*
G01X62000Y35324D02*
G01X62000Y34424D01*
G01X63399Y38824D02*
G01X63399Y37924D01*
G01X59050Y30124D02*
G01X59950Y30124D01*
G01X55600Y28925D02*
G01X55600Y29825D01*
G01X57000Y26324D02*
G01X57000Y25424D01*
G01X62000Y26324D02*
G01X62000Y25424D01*
G01X63399Y29825D02*
G01X63399Y28925D01*
G01X59050Y21124D02*
G01X59950Y21124D01*
G01X55600Y19925D02*
G01X55600Y20825D01*
G01X57000Y17324D02*
G01X57000Y16424D01*
G01X62000Y17324D02*
G01X62000Y16424D01*
G01X63399Y20825D02*
G01X63399Y19925D01*
G01X59050Y12124D02*
G01X59950Y12124D01*
G01X55600Y10925D02*
G01X55600Y11825D01*
G01X57000Y8324D02*
G01X57000Y7424D01*
G01X62000Y8324D02*
G01X62000Y7424D01*
G01X63399Y11825D02*
G01X63399Y10925D01*
G01X11875Y5050D02*
G01X11875Y5950D01*
G01X13075Y1600D02*
G01X12175Y1600D01*
G01X15675Y3000D02*
G01X16575Y3000D01*
G01X15675Y8000D02*
G01X16575Y8000D01*
G01X12175Y9399D02*
G01X13075Y9399D01*
G01X20875Y5050D02*
G01X20875Y5950D01*
G01X22075Y1600D02*
G01X21175Y1600D01*
G01X24675Y3000D02*
G01X25575Y3000D01*
G01X24675Y8000D02*
G01X25575Y8000D01*
G01X21175Y9399D02*
G01X22075Y9399D01*
G01X29875Y5050D02*
G01X29875Y5950D01*
G01X31075Y1600D02*
G01X30175Y1600D01*
G01X33675Y3000D02*
G01X34575Y3000D01*
G01X33675Y8000D02*
G01X34575Y8000D01*
G01X30175Y9399D02*
G01X31075Y9399D01*
G01X38875Y5050D02*
G01X38875Y5950D01*
G01X40075Y1600D02*
G01X39175Y1600D01*
G01X42675Y3000D02*
G01X43575Y3000D01*
G01X42675Y8000D02*
G01X43575Y8000D01*
G01X39175Y9399D02*
G01X40075Y9399D01*
G01X47875Y5050D02*
G01X47875Y5950D01*
G01X49074Y1600D02*
G01X48175Y1600D01*
G01X51675Y3000D02*
G01X52575Y3000D01*
G01X51675Y8000D02*
G01X52575Y8000D01*
G01X48175Y9399D02*
G01X49074Y9399D01*
M00*
D16*
G01*
X5663006Y-223012D03*
G01*
X5409006Y-477012D03*
G01*
X5409006Y-223012D03*
G01*
X5155006Y-477012D03*
G01*
X5155006Y-223012D03*
G01*
X4901006Y-477012D03*
G01*
X4901006Y-223012D03*
G01*
X4647006Y-477012D03*
G01*
X4647006Y-223012D03*
G01*
X4393006Y-477012D03*
G01*
X4393006Y-223012D03*
G01*
X4139006Y-477012D03*
G01*
X4139006Y-223012D03*
G01*
X3885006Y-477012D03*
G01*
X3885006Y-223012D03*
G01*
X3631006Y-477012D03*
G01*
X3631006Y-223012D03*
G01*
X3377006Y-477012D03*
G01*
X3377006Y-223012D03*
G01*
X3123006Y-477012D03*
G01*
X3123006Y-223012D03*
G01*
X2869006Y-477012D03*
G01*
X2869006Y-223012D03*
G01*
X2615006Y-477012D03*
G01*
X2615006Y-223012D03*
G01*
X2361006Y-477012D03*
G01*
X2361006Y-223012D03*
G01*
X2107006Y-477012D03*
G01*
X2107006Y-223012D03*
G01*
X1853006Y-477012D03*
G01*
X1853006Y-223012D03*
G01*
X1599006Y-477012D03*
G01*
X1599006Y-223012D03*
G01*
X1345006Y-477012D03*
G01*
X1345006Y-223012D03*
G01*
X1091006Y-477012D03*
G01*
X1091006Y-223012D03*
G01*
X837006Y-477012D03*
G01*
X837006Y-223012D03*
D12*
G01*
X3121990Y-1618005D03*
G01*
X3299993Y-2129993D03*
G01*
X3199993Y-1754987D03*
G01*
X3050006Y-1754733D03*
G01*
X3669995Y-3099993D03*
D17*
X3205007Y-3357524D02*
G01*
X3295007Y-3357524D01*
X2860014Y-3477503D02*
G01*
X2860014Y-3387506D01*
X3000019Y-3737518D02*
G01*
X3000019Y-3827515D01*
X3500018Y-3737518D02*
G01*
X3500018Y-3827515D01*
X3639997Y-3387506D02*
G01*
X3639997Y-3477503D01*
X505012Y-787527D02*
G01*
X595012Y-787527D01*
X160020Y-907508D02*
G01*
X160020Y-817509D01*
X300024Y-1167521D02*
G01*
X300024Y-1257520D01*
X800023Y-1167521D02*
G01*
X800023Y-1257520D01*
X940003Y-817509D02*
G01*
X940003Y-907508D01*
X505012Y-1687525D02*
G01*
X595012Y-1687525D01*
X160020Y-1807507D02*
G01*
X160020Y-1717507D01*
X300024Y-2067519D02*
G01*
X300024Y-2157519D01*
X800023Y-2067519D02*
G01*
X800023Y-2157519D01*
X940003Y-1717507D02*
G01*
X940003Y-1807507D01*
X505012Y-2587523D02*
G01*
X595012Y-2587523D01*
X160020Y-2707505D02*
G01*
X160020Y-2617505D01*
X300024Y-2967517D02*
G01*
X300024Y-3057517D01*
X800023Y-2967517D02*
G01*
X800023Y-3057517D01*
X940003Y-2617505D02*
G01*
X940003Y-2707505D01*
X505012Y-3487521D02*
G01*
X595012Y-3487521D01*
X160020Y-3607503D02*
G01*
X160020Y-3517503D01*
X300024Y-3867515D02*
G01*
X300024Y-3957515D01*
X800023Y-3867515D02*
G01*
X800023Y-3957515D01*
X940003Y-3517503D02*
G01*
X940003Y-3607503D01*
X505012Y-4387519D02*
G01*
X595012Y-4387519D01*
X160020Y-4507501D02*
G01*
X160020Y-4417501D01*
X300024Y-4767513D02*
G01*
X300024Y-4857513D01*
X800023Y-4767513D02*
G01*
X800023Y-4857513D01*
X940003Y-4417501D02*
G01*
X940003Y-4507501D01*
X5905002Y-787527D02*
G01*
X5995001Y-787527D01*
X5560009Y-907508D02*
G01*
X5560009Y-817509D01*
X5700013Y-1167521D02*
G01*
X5700013Y-1257520D01*
X6200013Y-1167521D02*
G01*
X6200013Y-1257520D01*
X6339992Y-817509D02*
G01*
X6339992Y-907508D01*
X5905002Y-1687525D02*
G01*
X5995001Y-1687525D01*
X5560009Y-1807507D02*
G01*
X5560009Y-1717507D01*
X5700013Y-2067519D02*
G01*
X5700013Y-2157519D01*
X6200013Y-2067519D02*
G01*
X6200013Y-2157519D01*
X6339992Y-1717507D02*
G01*
X6339992Y-1807507D01*
X5905002Y-2587523D02*
G01*
X5995001Y-2587523D01*
X5560009Y-2707505D02*
G01*
X5560009Y-2617505D01*
X5700013Y-2967517D02*
G01*
X5700013Y-3057517D01*
X6200013Y-2967517D02*
G01*
X6200013Y-3057517D01*
X6339992Y-2617505D02*
G01*
X6339992Y-2707505D01*
X5905002Y-3487521D02*
G01*
X5995001Y-3487521D01*
X5560009Y-3607503D02*
G01*
X5560009Y-3517503D01*
X5700013Y-3867515D02*
G01*
X5700013Y-3957515D01*
X6200013Y-3867515D02*
G01*
X6200013Y-3957515D01*
X6339992Y-3517503D02*
G01*
X6339992Y-3607503D01*
X5905002Y-4387519D02*
G01*
X5995001Y-4387519D01*
X5560009Y-4507501D02*
G01*
X5560009Y-4417501D01*
X5700013Y-4767513D02*
G01*
X5700013Y-4857513D01*
X6200013Y-4767513D02*
G01*
X6200013Y-4857513D01*
X6339992Y-4417501D02*
G01*
X6339992Y-4507501D01*
X1187526Y-5095003D02*
G01*
X1187526Y-5005003D01*
X1307505Y-5439994D02*
G01*
X1217508Y-5439994D01*
X1567520Y-5299989D02*
G01*
X1657517Y-5299989D01*
X1567520Y-4799990D02*
G01*
X1657517Y-4799990D01*
X1217508Y-4660011D02*
G01*
X1307505Y-4660011D01*
X2087524Y-5095003D02*
G01*
X2087524Y-5005003D01*
X2207503Y-5439994D02*
G01*
X2117506Y-5439994D01*
X2467518Y-5299989D02*
G01*
X2557515Y-5299989D01*
X2467518Y-4799990D02*
G01*
X2557515Y-4799990D01*
X2117506Y-4660011D02*
G01*
X2207503Y-4660011D01*
X2987522Y-5095003D02*
G01*
X2987522Y-5005003D01*
X3107502Y-5439994D02*
G01*
X3017504Y-5439994D01*
X3367516Y-5299989D02*
G01*
X3457514Y-5299989D01*
X3367516Y-4799990D02*
G01*
X3457514Y-4799990D01*
X3017504Y-4660011D02*
G01*
X3107502Y-4660011D01*
X3887520Y-5095003D02*
G01*
X3887520Y-5005003D01*
X4007500Y-5439994D02*
G01*
X3917502Y-5439994D01*
X4267514Y-5299989D02*
G01*
X4357512Y-5299989D01*
X4267514Y-4799990D02*
G01*
X4357512Y-4799990D01*
X3917502Y-4660011D02*
G01*
X4007500Y-4660011D01*
X4787518Y-5095003D02*
G01*
X4787518Y-5005003D01*
X4907498Y-5439994D02*
G01*
X4817501Y-5439994D01*
X5167513Y-5299989D02*
G01*
X5257510Y-5299989D01*
X5167513Y-4799990D02*
G01*
X5257510Y-4799990D01*
X4817501Y-4660011D02*
G01*
X4907498Y-4660011D01*
M02*

View File

@ -1,84 +1,88 @@
G04 Layer: BottomSilkLayer*
G04 EasyEDA v6.4.0, 2020-07-14T23:16:01--5:00*
G04 6dc5d916f8a9456ea10d5ff2c879efc5,9df6f537d2f94b3ba0ed850023b5714d,10*
G04 EasyEDA v6.4.25, 2022-04-08T13:20:52--5:00*
G04 Gerber Generator version 0.2*
G04 Scale: 100 percent, Rotated: No, Reflected: No *
G04 Dimensions in millimeters *
G04 leading zeros omitted , absolute positions ,3 integer and 3 decimal *
%FSLAX33Y33*%
G04 leading zeros omitted , absolute positions ,4 integer and 5 decimal *
%FSLAX45Y45*%
%MOMM*%
G90*
G71D02*
%ADD10C,0.254000*%
%ADD18C,0.152400*%
%ADD10C,0.2540*%
%ADD18C,0.1524*%
%LPD*%
G54D10*
G01X57899Y49960D02*
G01X57899Y55040D01*
G01X7099Y55040D01*
G01X7099Y49960D01*
G01X57899Y49960D01*
G01X57899Y52500D02*
G01X55359Y52500D01*
G01X55359Y49960D01*
G54D18*
G01X29560Y49253D02*
G01X29612Y49357D01*
G01X29716Y49461D01*
G01X29820Y49513D01*
G01X30028Y49513D01*
G01X30132Y49461D01*
G01X30236Y49357D01*
G01X30288Y49253D01*
G01X30339Y49097D01*
G01X30339Y48838D01*
G01X30288Y48682D01*
G01X30236Y48578D01*
G01X30132Y48474D01*
G01X30028Y48422D01*
G01X29820Y48422D01*
G01X29716Y48474D01*
G01X29612Y48578D01*
G01X29560Y48682D01*
G01X29560Y48838D01*
G01X29820Y48838D02*
G01X29560Y48838D01*
G01X29217Y49513D02*
G01X29217Y48422D01*
G01X29217Y49513D02*
G01X28750Y49513D01*
G01X28594Y49461D01*
G01X28542Y49409D01*
G01X28490Y49305D01*
G01X28490Y49149D01*
G01X28542Y49045D01*
G01X28594Y48993D01*
G01X28750Y48941D01*
G01X29217Y48941D01*
G01X28147Y49513D02*
G01X28147Y48422D01*
G01X27492Y49513D02*
G01X27596Y49461D01*
G01X27700Y49357D01*
G01X27752Y49253D01*
G01X27804Y49097D01*
G01X27804Y48838D01*
G01X27752Y48682D01*
G01X27700Y48578D01*
G01X27596Y48474D01*
G01X27492Y48422D01*
G01X27285Y48422D01*
G01X27181Y48474D01*
G01X27077Y48578D01*
G01X27025Y48682D01*
G01X26973Y48838D01*
G01X26973Y49097D01*
G01X27025Y49253D01*
G01X27077Y49357D01*
G01X27181Y49461D01*
G01X27285Y49513D01*
G01X27492Y49513D01*
M00*
D18*
X2956062Y-674636D02*
G01*
X2961259Y-664245D01*
X2971650Y-653854D01*
X2982038Y-648660D01*
X3002821Y-648660D01*
X3013212Y-653854D01*
X3023603Y-664245D01*
X3028800Y-674636D01*
X3033994Y-690224D01*
X3033994Y-716201D01*
X3028800Y-731786D01*
X3023603Y-742177D01*
X3013212Y-752568D01*
X3002821Y-757763D01*
X2982038Y-757763D01*
X2971650Y-752568D01*
X2961259Y-742177D01*
X2956062Y-731786D01*
X2956062Y-716201D01*
X2982038Y-716201D02*
G01*
X2956062Y-716201D01*
X2921772Y-648660D02*
G01*
X2921772Y-757763D01*
X2921772Y-648660D02*
G01*
X2875013Y-648660D01*
X2859427Y-653854D01*
X2854231Y-659051D01*
X2849036Y-669442D01*
X2849036Y-685027D01*
X2854231Y-695418D01*
X2859427Y-700613D01*
X2875013Y-705810D01*
X2921772Y-705810D01*
X2814746Y-648660D02*
G01*
X2814746Y-757763D01*
X2749283Y-648660D02*
G01*
X2759674Y-653854D01*
X2770065Y-664245D01*
X2775259Y-674636D01*
X2780456Y-690224D01*
X2780456Y-716201D01*
X2775259Y-731786D01*
X2770065Y-742177D01*
X2759674Y-752568D01*
X2749283Y-757763D01*
X2728501Y-757763D01*
X2718109Y-752568D01*
X2707718Y-742177D01*
X2702524Y-731786D01*
X2697330Y-716201D01*
X2697330Y-690224D01*
X2702524Y-674636D01*
X2707718Y-664245D01*
X2718109Y-653854D01*
X2728501Y-648660D01*
X2749283Y-648660D01*
D10*
X5789998Y-603999D02*
G01*
X5789998Y-95999D01*
X709998Y-95999D01*
X709998Y-603999D01*
X5789998Y-603999D01*
X5789998Y-349999D02*
G01*
X5535998Y-349999D01*
X5535998Y-603999D01*
M02*

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +1,38 @@
;Layer: Drill NPTH
;EasyEDA v6.4.0, 2020-07-14T23:16:01--5:00
;6dc5d916f8a9456ea10d5ff2c879efc5,9df6f537d2f94b3ba0ed850023b5714d,10
;Gerber Generator version 0.2
M48
METRIC,LZ,000.000
;Holesize 1 = 1.301 METRIC
;FILE_FORMAT=3:3
;TYPE=NON_PLATED
;Layer: Drill NPTH
;EasyEDA v6.4.25, 2022-04-08T13:20:52--5:00
;10
;Gerber Generator version 0.2
;Holesize 1 = 1.301 mm
T01C1.301
;Holesize 2 = 2.751 METRIC
;Holesize 2 = 2.751 mm
T02C2.751
%
G05
G90
T01
X+032500Y+017424
X+005500Y+043124
X+005500Y+034124
X+005500Y+025124
X+005500Y+016124
X+005500Y+007124
X+059500Y+043124
X+059500Y+034124
X+059500Y+025124
X+059500Y+016124
X+059500Y+007124
X+016875Y+005500
X+025875Y+005500
X+034875Y+005500
X+043875Y+005500
X+052875Y+005500
X032500Y-038575
X005500Y-012875
X005500Y-021875
X005500Y-030875
X005500Y-039875
X005500Y-048875
X059500Y-012875
X059500Y-021875
X059500Y-030875
X059500Y-039875
X059500Y-048875
X016875Y-050500
X025875Y-050500
X034875Y-050500
X043875Y-050500
X052875Y-050500
T02
X+061500Y+052499
X+061500Y+003500
X+003500Y+052499
X+003500Y+003500
X061500Y-003500
X061500Y-052500
X003500Y-003500
X003500Y-052500
M30

View File

@ -1,198 +1,200 @@
;Layer: Drill PTH
;EasyEDA v6.4.0, 2020-07-14T23:16:01--5:00
;6dc5d916f8a9456ea10d5ff2c879efc5,9df6f537d2f94b3ba0ed850023b5714d,10
;Gerber Generator version 0.2
M48
METRIC,LZ,000.000
;Holesize 1 = 0.305 METRIC
;FILE_FORMAT=3:3
;TYPE=PLATED
;Layer: Drill PTH
;EasyEDA v6.4.25, 2022-04-08T13:20:52--5:00
;10
;Gerber Generator version 0.2
;Holesize 1 = 0.305 mm
T01C0.305
;Holesize 2 = 0.800 METRIC
;Holesize 2 = 0.800 mm
T02C0.800
;Holesize 3 = 1.000 METRIC
;Holesize 3 = 1.000 mm
T03C1.000
;Holesize 4 = 1.101 METRIC
;Holesize 4 = 1.101 mm
T04C1.101
;Holesize 5 = 1.200 METRIC
;Holesize 5 = 1.200 mm
T05C1.200
%
G05
G90
T01
X+031219Y+039820
X+032999Y+034700
X+031999Y+038450
X+030500Y+038452
X+036699Y+025000
X031220Y-016180
X033000Y-021300
X032000Y-017550
X030500Y-017547
X036700Y-031000
T02
X+032950Y+022425G85X+032049Y+022425
X+028600Y+022125G85X+028600Y+021225
X+030000Y+017725G85X+030000Y+018625
X+035000Y+017725G85X+035000Y+018625
X+036400Y+021225G85X+036400Y+022125
X+049999Y+019000
X+051500Y+019000
X+013949Y+013380
X+015450Y+013380
X+005950Y+048125G85X+005049Y+048125
X+001599Y+047825G85X+001599Y+046925
X+002999Y+043425G85X+002999Y+044325
X+007999Y+043425G85X+007999Y+044325
X+009399Y+046925G85X+009399Y+047825
X+005950Y+039125G85X+005049Y+039125
X+001599Y+038825G85X+001599Y+037925
X+002999Y+034425G85X+002999Y+035325
X+007999Y+034425G85X+007999Y+035325
X+009399Y+037925G85X+009399Y+038825
X+005950Y+030125G85X+005049Y+030125
X+001599Y+029825G85X+001599Y+028925
X+002999Y+025425G85X+002999Y+026325
X+007999Y+025425G85X+007999Y+026325
X+009399Y+028925G85X+009399Y+029825
X+005950Y+021125G85X+005049Y+021125
X+001599Y+020825G85X+001599Y+019925
X+002999Y+016425G85X+002999Y+017325
X+007999Y+016425G85X+007999Y+017325
X+009399Y+019925G85X+009399Y+020825
X+005950Y+012125G85X+005049Y+012125
X+001599Y+011825G85X+001599Y+010925
X+002999Y+007425G85X+002999Y+008325
X+007999Y+007425G85X+007999Y+008325
X+009399Y+010925G85X+009399Y+011825
X+059950Y+048125G85X+059049Y+048125
X+055600Y+047825G85X+055600Y+046925
X+057000Y+043425G85X+057000Y+044325
X+062000Y+043425G85X+062000Y+044325
X+063400Y+046925G85X+063400Y+047825
X+059950Y+039125G85X+059049Y+039125
X+055600Y+038825G85X+055600Y+037925
X+057000Y+034425G85X+057000Y+035325
X+062000Y+034425G85X+062000Y+035325
X+063400Y+037925G85X+063400Y+038825
X+059950Y+030125G85X+059049Y+030125
X+055600Y+029825G85X+055600Y+028925
X+057000Y+025425G85X+057000Y+026325
X+062000Y+025425G85X+062000Y+026325
X+063400Y+028925G85X+063400Y+029825
X+059950Y+021125G85X+059049Y+021125
X+055600Y+020825G85X+055600Y+019925
X+057000Y+016425G85X+057000Y+017325
X+062000Y+016425G85X+062000Y+017325
X+063400Y+019925G85X+063400Y+020825
X+059950Y+012125G85X+059049Y+012125
X+055600Y+011825G85X+055600Y+010925
X+057000Y+007425G85X+057000Y+008325
X+062000Y+007425G85X+062000Y+008325
X+063400Y+010925G85X+063400Y+011825
X+011874Y+005950G85X+011874Y+005050
X+012174Y+001600G85X+013074Y+001600
X+016574Y+003000G85X+015674Y+003000
X+016574Y+008000G85X+015674Y+008000
X+013074Y+009400G85X+012174Y+009400
X+020874Y+005950G85X+020874Y+005050
X+021174Y+001600G85X+022074Y+001600
X+025575Y+003000G85X+024674Y+003000
X+025575Y+008000G85X+024674Y+008000
X+022074Y+009400G85X+021174Y+009400
X+029874Y+005950G85X+029874Y+005050
X+030174Y+001600G85X+031074Y+001600
X+034575Y+003000G85X+033674Y+003000
X+034575Y+008000G85X+033674Y+008000
X+031074Y+009400G85X+030174Y+009400
X+038874Y+005950G85X+038874Y+005050
X+039174Y+001600G85X+040074Y+001600
X+043574Y+003000G85X+042674Y+003000
X+043574Y+008000G85X+042674Y+008000
X+040074Y+009400G85X+039174Y+009400
X+047874Y+005950G85X+047874Y+005050
X+048174Y+001600G85X+049074Y+001600
X+052575Y+003000G85X+051674Y+003000
X+052575Y+008000G85X+051674Y+008000
X+049074Y+009400G85X+048174Y+009400
X032950Y-033575G85X032050Y-033575
X028600Y-033875G85X028600Y-034775
X030000Y-038275G85X030000Y-037375
X035000Y-038275G85X035000Y-037375
X036400Y-034775G85X036400Y-033875
X049999Y-037000
X051501Y-037000
X013949Y-042620
X015451Y-042620
X005950Y-007875G85X005050Y-007875
X001600Y-008175G85X001600Y-009075
X003000Y-012575G85X003000Y-011675
X008000Y-012575G85X008000Y-011675
X009400Y-009075G85X009400Y-008175
X005950Y-016875G85X005050Y-016875
X001600Y-017175G85X001600Y-018075
X003000Y-021575G85X003000Y-020675
X008000Y-021575G85X008000Y-020675
X009400Y-018075G85X009400Y-017175
X005950Y-025875G85X005050Y-025875
X001600Y-026175G85X001600Y-027075
X003000Y-030575G85X003000Y-029675
X008000Y-030575G85X008000Y-029675
X009400Y-027075G85X009400Y-026175
X005950Y-034875G85X005050Y-034875
X001600Y-035175G85X001600Y-036075
X003000Y-039575G85X003000Y-038675
X008000Y-039575G85X008000Y-038675
X009400Y-036075G85X009400Y-035175
X005950Y-043875G85X005050Y-043875
X001600Y-044175G85X001600Y-045075
X003000Y-048575G85X003000Y-047675
X008000Y-048575G85X008000Y-047675
X009400Y-045075G85X009400Y-044175
X059950Y-007875G85X059050Y-007875
X055600Y-008175G85X055600Y-009075
X057000Y-012575G85X057000Y-011675
X062000Y-012575G85X062000Y-011675
X063400Y-009075G85X063400Y-008175
X059950Y-016875G85X059050Y-016875
X055600Y-017175G85X055600Y-018075
X057000Y-021575G85X057000Y-020675
X062000Y-021575G85X062000Y-020675
X063400Y-018075G85X063400Y-017175
X059950Y-025875G85X059050Y-025875
X055600Y-026175G85X055600Y-027075
X057000Y-030575G85X057000Y-029675
X062000Y-030575G85X062000Y-029675
X063400Y-027075G85X063400Y-026175
X059950Y-034875G85X059050Y-034875
X055600Y-035175G85X055600Y-036075
X057000Y-039575G85X057000Y-038675
X062000Y-039575G85X062000Y-038675
X063400Y-036075G85X063400Y-035175
X059950Y-043875G85X059050Y-043875
X055600Y-044175G85X055600Y-045075
X057000Y-048575G85X057000Y-047675
X062000Y-048575G85X062000Y-047675
X063400Y-045075G85X063400Y-044175
X011875Y-050050G85X011875Y-050950
X012175Y-054400G85X013075Y-054400
X016575Y-053000G85X015675Y-053000
X016575Y-048000G85X015675Y-048000
X013075Y-046600G85X012175Y-046600
X020875Y-050050G85X020875Y-050950
X021175Y-054400G85X022075Y-054400
X025575Y-053000G85X024675Y-053000
X025575Y-048000G85X024675Y-048000
X022075Y-046600G85X021175Y-046600
X029875Y-050050G85X029875Y-050950
X030175Y-054400G85X031075Y-054400
X034575Y-053000G85X033675Y-053000
X034575Y-048000G85X033675Y-048000
X031075Y-046600G85X030175Y-046600
X038875Y-050050G85X038875Y-050950
X039175Y-054400G85X040075Y-054400
X043575Y-053000G85X042675Y-053000
X043575Y-048000G85X042675Y-048000
X040075Y-046600G85X039175Y-046600
X047875Y-050050G85X047875Y-050950
X048175Y-054400G85X049075Y-054400
X052575Y-053000G85X051675Y-053000
X052575Y-048000G85X051675Y-048000
X049075Y-046600G85X048175Y-046600
T03
X+012999Y+036730
X+012999Y+047030
X+052539Y+027390
X+052539Y+037690
X+042250Y+023900
X+052550Y+023900
X+012999Y+018010
X+012999Y+028310
X+039600Y+012550
X+039600Y+022850
X+024350Y+014200
X+034650Y+014200
X013000Y-019270
X013000Y-008970
X052540Y-028610
X052540Y-018310
X042250Y-032100
X052550Y-032100
X013000Y-037990
X013000Y-027690
X039600Y-043450
X039600Y-033150
X024350Y-041800
X034650Y-041800
T04
X+020690Y+044890
X+020690Y+042350
X+020690Y+034730
X+020690Y+032190
X+020690Y+039810
X+020690Y+037270
X+020690Y+029650
X+020690Y+027110
X+028310Y+027110
X+028310Y+029650
X+028310Y+032190
X+028310Y+034730
X+028310Y+037270
X+028310Y+039810
X+028310Y+042350
X+028310Y+044890
X+044309Y+027110
X+044309Y+029650
X+044309Y+037270
X+044309Y+039810
X+044309Y+032190
X+044309Y+034730
X+044309Y+042350
X+044309Y+044890
X+036689Y+044890
X+036689Y+042350
X+036689Y+039810
X+036689Y+037270
X+036689Y+034730
X+036689Y+032190
X+036689Y+029650
X+036689Y+027110
X020690Y-011110
X020690Y-013650
X020690Y-021270
X020690Y-023810
X020690Y-016190
X020690Y-018730
X020690Y-026350
X020690Y-028890
X028310Y-028890
X028310Y-026350
X028310Y-023810
X028310Y-021270
X028310Y-018730
X028310Y-016190
X028310Y-013650
X028310Y-011110
X044310Y-028890
X044310Y-026350
X044310Y-018730
X044310Y-016190
X044310Y-023810
X044310Y-021270
X044310Y-013650
X044310Y-011110
X036690Y-011110
X036690Y-013650
X036690Y-016190
X036690Y-018730
X036690Y-021270
X036690Y-023810
X036690Y-026350
X036690Y-028890
T05
X+056630Y+051230
X+056630Y+053770
X+054090Y+051230
X+054090Y+053770
X+051550Y+051230
X+051550Y+053770
X+049010Y+051230
X+049010Y+053770
X+046470Y+051230
X+046470Y+053770
X+043930Y+051230
X+043930Y+053770
X+041390Y+051230
X+041390Y+053770
X+038850Y+051230
X+038850Y+053770
X+036310Y+051230
X+036310Y+053770
X+033770Y+051230
X+033770Y+053770
X+031230Y+051230
X+031230Y+053770
X+028690Y+051230
X+028690Y+053770
X+026150Y+051230
X+026150Y+053770
X+023610Y+051230
X+023610Y+053770
X+021070Y+051230
X+021070Y+053770
X+018530Y+051230
X+018530Y+053770
X+015990Y+051230
X+015990Y+053770
X+013450Y+051230
X+013450Y+053770
X+010910Y+051230
X+010910Y+053770
X+008370Y+051230
X+008370Y+053770
X056630Y-004770
X056630Y-002230
X054090Y-004770
X054090Y-002230
X051550Y-004770
X051550Y-002230
X049010Y-004770
X049010Y-002230
X046470Y-004770
X046470Y-002230
X043930Y-004770
X043930Y-002230
X041390Y-004770
X041390Y-002230
X038850Y-004770
X038850Y-002230
X036310Y-004770
X036310Y-002230
X033770Y-004770
X033770Y-002230
X031230Y-004770
X031230Y-002230
X028690Y-004770
X028690Y-002230
X026150Y-004770
X026150Y-002230
X023610Y-004770
X023610Y-002230
X021070Y-004770
X021070Y-002230
X018530Y-004770
X018530Y-002230
X015990Y-004770
X015990Y-002230
X013450Y-004770
X013450Y-002230
X010910Y-004770
X010910Y-002230
X008370Y-004770
X008370Y-002230
M30

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
pcb/Gerber/LPMPCB.zip Normal file

Binary file not shown.

View File

@ -0,0 +1,68 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>lantern-pigpio</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>lantern-pigpio</name>
<parent>
<groupId>com.lanternsoftware.pigpio</groupId>
<artifactId>pigpio</artifactId>
<version>1.0.0</version>
</parent>
<dependencies>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-common</artifactId>
<version>${util.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.29</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<optimize>true</optimize>
<showDeprecation>true</showDeprecation>
<encoding>UTF-8</encoding>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,52 @@
package com.lanternsoftware.pigpio;
import com.lanternsoftware.util.ResourceLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PIGPIO {
protected static final Logger LOG = LoggerFactory.getLogger(PIGPIO.class);
private PIGPIO() {
}
static {
try {
String osArch = System.getProperty("os.arch").toLowerCase();
if (osArch.equals("arm"))
osArch = "armhf";
String path = "/lib/" + osArch + "/lantern-pigpio.so";
byte[] file = ResourceLoader.getByteArrayResource(PIGPIO.class, path);
LOG.info("library size: {}", file.length);
String libPath = "/opt/currentmonitor/lantern-pigpio.so";
ResourceLoader.writeFile(libPath, file);
System.load(libPath);
} catch (Exception _e) {
LOG.error("Failed to load lantern-pigpio.so from resource", _e);
}
}
public static native int gpioInitialise();
public static native void gpioTerminate();
public static native int gpioSetMode(int gpio, int mode);
public static native int gpioGetMode(int gpio);
public static native int gpioSetPullUpDown(int gpio, int pud);
public static native int gpioRead(int gpio);
public static native int gpioWrite(int gpio, int level);
public static native int spiOpen(int spiChan, int baud, int spiFlags);
public static native int spiClose(int handle);
public static native int spiRead(int handle, byte[] buf, int offset, int count);
public static native int spiWrite(int handle, byte[] buf, int offset, int count);
public static native int spiXfer(int handle, byte[] txBuf, int txOffset, byte[] rxBuf, int rxOffset, int count);
}

View File

@ -0,0 +1,53 @@
package com.lanternsoftware.pigpio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class PiGpioFactory {
private static final Logger LOG = LoggerFactory.getLogger(PIGPIO.class);
private static final Map<Integer, Spi> spiHandles = new HashMap<>();
private static boolean initialized = false;
public static Spi getSpiChannel(int _channel, int _baud, boolean _auxiliary) {
if (!ensureInitialized())
return null;
int channelId = (0xff & _channel);
if (_auxiliary)
channelId |= 0x100;
Spi handle = spiHandles.get(channelId);
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;
}
LOG.error("Failed to get SPI handle");
return null;
}
private static boolean ensureInitialized() {
if (initialized)
return true;
int init = PIGPIO.gpioInitialise();
LOG.info("GPIO init: {}", init);
if (init < 0) {
LOG.error("Failed to initialize PiGpio");
return false;
}
initialized = true;
return true;
}
public static void shutdown() {
for (Spi handle : spiHandles.values()) {
PIGPIO.spiClose(handle.getHandle());
}
spiHandles.clear();
PIGPIO.gpioTerminate();
}
}

View File

@ -0,0 +1,37 @@
package com.lanternsoftware.pigpio;
public class Spi {
private final int handle;
public Spi(int _handle) {
handle = _handle;
}
public int getHandle() {
return handle;
}
public int read(byte[] buf) {
return read(buf, 0, buf.length);
}
public int read(byte[] buf, int offset, int count) {
return PIGPIO.spiRead(handle, buf, offset, count);
}
public int write(byte[] buf) {
return write(buf, 0, buf.length);
}
public int write(byte[] buf, int offset, int count) {
return PIGPIO.spiWrite(handle, buf, offset, count);
}
public int transfer(byte[] txBuf, byte[] rxBuf) {
return transfer(txBuf, 0, rxBuf, 0, rxBuf.length);
}
public int transfer(byte[] txBuf, int txOffset, byte[] rxBuf, int rxOffset, int count) {
return PIGPIO.spiXfer(handle, txBuf, txOffset, rxBuf, rxOffset, count);
}
}

View File

@ -0,0 +1,54 @@
ARCH := armhf
DEBUG = -O3
CC = $(CROSS_PREFIX)gcc
AR = $(CROSS_PREFIX)ar
RANLIB = $(CROSS_PREFIX)ranlib
SIZE = $(CROSS_PREFIX)size
STRIP = $(CROSS_PREFIX)strip
SHLIB = $(CC) -shared
STRIPLIB = $(STRIP) --strip-unneeded
INCLUDE = -I. -Ipigpio \
-I/$(JAVA_HOME)/include \
-I/$(JAVA_HOME)/include/linux \
-I/usr/local/include -I/usr/local/include/linux
CFLAGS := $(DEBUG) -Wall $(INCLUDE) -Winline -pipe $(CARGS) -fPIC
LIBS = -L lib/$(ARCH) -L pigpio -lpigpio -lrt
TARGET=lantern-pigpio.so
###############################################################################
SRC = com_lanternsoftware_pigpio_PIGPIO.c
OBJ = $(SRC:.c=.o)
all: $(OBJ)
@echo [LINK with DYNAMICALLY linked libraries]
@$(CC) $(OBJ) -shared -o $(TARGET) $(INCLUDE) $(LIBS)
.c.o:
@echo [COMPILE] $<
@$(CC) -c $(CFLAGS) $< -o $@
clean:
rm -f $(OBJ) $(TARGET) *~ core tags Makefile.bak
tags: $(SRC)
@echo [ctags]
@ctags $(SRC)
depend:
makedepend -Y $(SRC)
install: $(TARGET)
@echo [install]
install -m 0755 -d /usr/local/lib
install -m 0755 -d /usr/local/include
install -m 0644 $(TARGET) /usr/local/lib
uninstall:
@echo [uninstall]
rm -f /usr/local/lib/$(TARGET)
com_lanternsoftware_pigpio_PIGPIO.o: com_lanternsoftware_pigpio_PIGPIO.h

View File

@ -0,0 +1,117 @@
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <pigpio.h>
#include "com_lanternsoftware_pigpio_PIGPIO.h"
JavaVM *callback_jvm;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2))
{
return JNI_ERR;
}
callback_jvm = jvm;
return JNI_VERSION_1_2;
}
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *jvm, void *reserved)
{
return;
}
JNIEXPORT jint JNICALL Java_com_lanternsoftware_pigpio_PIGPIO_gpioInitialise
(JNIEnv *env, jclass class)
{
gpioCfgSetInternals (gpioCfgGetInternals () | PI_CFG_NOSIGHANDLER);
return gpioInitialise();
}
JNIEXPORT void JNICALL Java_com_lanternsoftware_pigpio_PIGPIO_gpioTerminate(JNIEnv *env, jclass class)
{
return gpioTerminate();
}
JNIEXPORT jint JNICALL Java_com_lanternsoftware_pigpio_PIGPIO_gpioSetMode
(JNIEnv *env, jclass class, jint gpio, jint mode)
{
return gpioSetMode((unsigned)gpio, (unsigned)mode);
}
JNIEXPORT jint JNICALL Java_com_lanternsoftware_pigpio_PIGPIO_gpioGetMode
(JNIEnv *env, jclass class, jint gpio)
{
return gpioGetMode((unsigned)gpio);
}
JNIEXPORT jint JNICALL Java_com_lanternsoftware_pigpio_PIGPIO_gpioSetPullUpDown
(JNIEnv *env, jclass class, jint gpio, jint pud)
{
return gpioSetPullUpDown((unsigned)gpio, (unsigned)pud);
}
JNIEXPORT jint JNICALL Java_com_lanternsoftware_pigpio_PIGPIO_gpioRead
(JNIEnv *env, jclass class, jint gpio)
{
return gpioRead((unsigned)gpio);
}
JNIEXPORT jint JNICALL Java_com_lanternsoftware_pigpio_PIGPIO_gpioWrite
(JNIEnv *env, jclass class, jint gpio, jint level)
{
return gpioWrite((unsigned)gpio, (unsigned)level);
}
JNIEXPORT jint JNICALL Java_com_lanternsoftware_pigpio_PIGPIO_spiOpen
(JNIEnv *env, jclass class, jint spiChan, jint baud, jint spiFlags)
{
return spiOpen((unsigned)spiChan, (unsigned)baud, (unsigned)spiFlags);
}
JNIEXPORT jint JNICALL Java_com_lanternsoftware_pigpio_PIGPIO_spiClose
(JNIEnv *env, jclass class, jint handle)
{
return spiClose((unsigned)handle);
}
JNIEXPORT jint JNICALL Java_com_lanternsoftware_pigpio_PIGPIO_spiRead
(JNIEnv *env, jclass class, jint handle, jbyteArray data, jint offset, jint count)
{
jbyte *buffer = (*env)->GetByteArrayElements(env, data, 0);
jsize max_length = (*env)->GetArrayLength(env, data) - offset;
int length = (count > max_length) ? max_length : count;
jbyte *offsetBuffer = buffer + offset;
jint result = spiRead((unsigned)handle, (char *)offsetBuffer, (unsigned)length);
(*env)->ReleaseByteArrayElements(env, data, buffer, 0);
return result;
}
JNIEXPORT jint JNICALL Java_com_lanternsoftware_pigpio_PIGPIO_spiWrite
(JNIEnv *env, jclass class, jint handle, jbyteArray data, jint offset, jint count)
{
jbyte *buffer = (*env)->GetByteArrayElements(env, data, 0);
jsize max_length = (*env)->GetArrayLength(env, data) - offset;
int length = (count > max_length) ? max_length : count;
jbyte *offsetBuffer = buffer + offset;
jint result = spiWrite((unsigned)handle, (char *)offsetBuffer, (unsigned)length);
(*env)->ReleaseByteArrayElements(env, data, buffer, JNI_ABORT);
return result;
}
JNIEXPORT jint JNICALL Java_com_lanternsoftware_pigpio_PIGPIO_spiXfer
(JNIEnv *env, jclass class, jint handle, jbyteArray writeData, jint writeOffset, jbyteArray readData, jint readOffset, jint count)
{
jbyte *writeBuffer = (*env)->GetByteArrayElements(env, writeData, 0);
jbyte *readBuffer = (*env)->GetByteArrayElements(env, readData, 0);
jsize max_length = (*env)->GetArrayLength(env, writeData) - writeOffset;
int length = (count > max_length) ? max_length : count;
jbyte *offsetWriteBuffer = writeBuffer + writeOffset;
jbyte *offsetReadBuffer = readBuffer + readOffset;
jint result = spiXfer((unsigned)handle, (char *)offsetWriteBuffer, (char *)offsetReadBuffer, (unsigned)length);
(*env)->ReleaseByteArrayElements(env, writeData, writeBuffer, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, readData, readBuffer, 0);
return result;
}

Some files were not shown because too many files have changed in this diff Show More