initial commit
This commit is contained in:
85
pom.xml
Normal file
85
pom.xml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.7.3</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>space.hilltopgrove</groupId>
|
||||||
|
<artifactId>lightswitch</artifactId>
|
||||||
|
<version>0.0.2-SNAPSHOT</version>
|
||||||
|
<name>lightswitch</name>
|
||||||
|
<description>Demo project for Spring Boot</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<pi4j.version>2.1.1</pi4j.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- include Pi4J Core -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.pi4j</groupId>
|
||||||
|
<artifactId>pi4j-core</artifactId>
|
||||||
|
<version>${pi4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- include Pi4J Plugins (Platforms and I/O Providers) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.pi4j</groupId>
|
||||||
|
<artifactId>pi4j-plugin-raspberrypi</artifactId>
|
||||||
|
<version>${pi4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.pi4j</groupId>
|
||||||
|
<artifactId>pi4j-plugin-pigpio</artifactId>
|
||||||
|
<version>${pi4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/application.yml</exclude>
|
||||||
|
</excludes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package space.hilltopgrove.lightswitch;
|
||||||
|
|
||||||
|
import com.pi4j.context.Context;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||||
|
|
||||||
|
import javax.annotation.PreDestroy;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@ConfigurationPropertiesScan
|
||||||
|
public class LightswitchApplication {
|
||||||
|
private final Context pi4jContext;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(LightswitchApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void shutdown() {
|
||||||
|
pi4jContext.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.config;
|
||||||
|
|
||||||
|
import com.luckycatlabs.sunrisesunset.SunriseSunsetCalculator;
|
||||||
|
import com.luckycatlabs.sunrisesunset.dto.Location;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class CalculatorConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SunriseSunsetCalculator calculator(PropertiesConfig propertiesConfig) {
|
||||||
|
return new SunriseSunsetCalculator(getLocation(propertiesConfig), TimeZone.getDefault().getID());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Location getLocation(PropertiesConfig propertiesConfig) {
|
||||||
|
return new Location(propertiesConfig.getLatitude(), propertiesConfig.getLongitude());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.config;
|
||||||
|
|
||||||
|
import com.pi4j.Pi4J;
|
||||||
|
import com.pi4j.context.Context;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class Pi4JContext {
|
||||||
|
@Bean
|
||||||
|
public Context pi4jContext() {
|
||||||
|
return Pi4J.newAutoContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties
|
||||||
|
public class PropertiesConfig {
|
||||||
|
private Map<Integer, Map<Integer, Integer>> shelf;
|
||||||
|
private double latitude;
|
||||||
|
private double longitude;
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.config;
|
||||||
|
|
||||||
|
import com.pi4j.context.Context;
|
||||||
|
import com.pi4j.io.gpio.digital.DigitalOutput;
|
||||||
|
import com.pi4j.io.gpio.digital.DigitalState;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import space.hilltopgrove.lightswitch.model.Light;
|
||||||
|
import space.hilltopgrove.lightswitch.model.Rack;
|
||||||
|
import space.hilltopgrove.lightswitch.model.Shelf;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class ShelfConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Shelf shelf(PropertiesConfig propertiesConfig, Context pi4jContext) {
|
||||||
|
var rawShelfMap = propertiesConfig.getShelf();
|
||||||
|
log.info("converting raw shelfMap: {}", rawShelfMap);
|
||||||
|
var rackMap = getRackMap(rawShelfMap, pi4jContext);
|
||||||
|
return Shelf.map(rackMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, Rack> getRackMap(Map<Integer, Map<Integer, Integer>> rawRackMap, Context pi4jContext) {
|
||||||
|
var result = rawRackMap.keySet().stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
rackKey -> rackKey,
|
||||||
|
rackKey -> Rack.map(getLightMap(rawRackMap.get(rackKey), pi4jContext))
|
||||||
|
));
|
||||||
|
log.info("rackMap: " + result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, Light> getLightMap(Map<Integer, Integer> rawLightMap, Context pi4jContext) {
|
||||||
|
var result = rawLightMap.keySet().stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
lightKey -> lightKey,
|
||||||
|
lightKey -> Light.map(createDigitalOutput(rawLightMap.get(lightKey), pi4jContext))));
|
||||||
|
log.info("lightMap: " + result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DigitalOutput createDigitalOutput(int pin, Context pi4jContext) {
|
||||||
|
return pi4jContext.create(
|
||||||
|
DigitalOutput.newConfigBuilder(pi4jContext)
|
||||||
|
.id("led" + pin)
|
||||||
|
.name("LED" + pin)
|
||||||
|
.address(pin)
|
||||||
|
.shutdown(DigitalState.LOW)
|
||||||
|
.initial(DigitalState.LOW)
|
||||||
|
.provider("pigpio-digital-output")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.controller;
|
||||||
|
|
||||||
|
public interface LightingAPI {
|
||||||
|
void on(Integer rack, Integer pin);
|
||||||
|
void off(Integer rack, Integer pin);
|
||||||
|
void toggle(Integer rack, Integer pin);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.controller.impl;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import space.hilltopgrove.lightswitch.controller.LightingAPI;
|
||||||
|
import space.hilltopgrove.lightswitch.model.Shelf;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping(path = "/lights/api/v2")
|
||||||
|
public class LightingController implements LightingAPI {
|
||||||
|
|
||||||
|
private final Shelf shelf;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PostMapping(path = {"/on", "/on/{rack}", "/on/{rack}/{light}"})
|
||||||
|
public void on(
|
||||||
|
@PathVariable(value = "rack", required = false)
|
||||||
|
Integer rack,
|
||||||
|
@PathVariable(value = "light", required = false)
|
||||||
|
Integer pin) {
|
||||||
|
shelf.on(rack, pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PostMapping(path = {"/off", "/off/{rack}", "/off/{rack}/{light}"})
|
||||||
|
public void off(
|
||||||
|
@PathVariable(value = "rack", required = false)
|
||||||
|
Integer rack,
|
||||||
|
@PathVariable(value = "light", required = false)
|
||||||
|
Integer pin) {
|
||||||
|
shelf.off(rack, pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PostMapping(path = {"/toggle", "/toggle/{rack}", "/toggle/{rack}/{light}"})
|
||||||
|
public void toggle(
|
||||||
|
@PathVariable(value = "rack", required = false)
|
||||||
|
Integer rack,
|
||||||
|
@PathVariable(value = "light", required = false)
|
||||||
|
Integer pin) {
|
||||||
|
shelf.toggle(rack, pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.controller.impl;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import space.hilltopgrove.lightswitch.model.Shelf;
|
||||||
|
import space.hilltopgrove.lightswitch.service.SunRiseSunSetService;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@EnableScheduling
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ScheduleController {
|
||||||
|
|
||||||
|
private final SunRiseSunSetService sunRiseSunSetService;
|
||||||
|
private final Shelf shelf;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void runAtPostConstruct() {
|
||||||
|
setSchedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(cron = "${cronExpression}")
|
||||||
|
public void newDay() {
|
||||||
|
log.info("newDay::Started");
|
||||||
|
setSchedule();
|
||||||
|
log.info("newDay::Completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSchedule() {
|
||||||
|
shelf.setRacksSchedule(sunRiseSunSetService.getSchedule());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.model;
|
||||||
|
|
||||||
|
import com.pi4j.io.gpio.digital.DigitalOutput;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Slf4j
|
||||||
|
@Builder
|
||||||
|
public class Light {
|
||||||
|
private DigitalOutput light;
|
||||||
|
|
||||||
|
public static Light map(DigitalOutput digitalOutput) {
|
||||||
|
return Light.builder()
|
||||||
|
.light(digitalOutput)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAsDawnLight(ScheduleBean scheduleBean) {
|
||||||
|
if (scheduleBean.isNowBeforeDawn()) {
|
||||||
|
log.info("{}: Seconds until dawn: {}", this.light.getName(), scheduleBean.nowTillDawn());
|
||||||
|
secondsTillOn(scheduleBean.nowTillDawn());
|
||||||
|
log.info("{}: Seconds until dusk: {}", this.light.getName(), scheduleBean.nowTillDusk());
|
||||||
|
secondsTillOff(scheduleBean.nowTillDusk());
|
||||||
|
}
|
||||||
|
if (scheduleBean.isNowBetweenDawnAndDusk()) {
|
||||||
|
this.on();
|
||||||
|
log.info("{}: Seconds until dusk: {}", this.light.getName(), scheduleBean.nowTillDusk());
|
||||||
|
secondsTillOff(scheduleBean.nowTillDusk());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAsSunriseLight(ScheduleBean scheduleBean) {
|
||||||
|
if (scheduleBean.isNowBeforeSunrise()) {
|
||||||
|
log.info("{}: Seconds until sunrise: {}", this.light.getName(), scheduleBean.nowTillSunrise());
|
||||||
|
secondsTillOn(scheduleBean.nowTillSunrise());
|
||||||
|
log.info("{}: Seconds until sunset: {}", this.light.getName(), scheduleBean.nowTillSunset());
|
||||||
|
secondsTillOff(scheduleBean.nowTillSunset());
|
||||||
|
}
|
||||||
|
if (scheduleBean.isNowBetweenSunriseAndSunset()) {
|
||||||
|
this.on();
|
||||||
|
log.info("{}: Seconds until sunset: {}", this.light.getName(), scheduleBean.nowTillSunset());
|
||||||
|
secondsTillOff(scheduleBean.nowTillSunset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAsMorningLight(ScheduleBean scheduleBean) {
|
||||||
|
if (scheduleBean.isNowBeforeMorning()) {
|
||||||
|
log.info("{}: Seconds until morning: {}", this.light.getName(), scheduleBean.nowTillMorning());
|
||||||
|
secondsTillOn(scheduleBean.nowTillMorning());
|
||||||
|
log.info("{}: Seconds until afternoon: {}", this.light.getName(), scheduleBean.nowTillAfternoon());
|
||||||
|
secondsTillOff(scheduleBean.nowTillAfternoon());
|
||||||
|
}
|
||||||
|
if (scheduleBean.isNowBetweenMorningAndAfternoon()) {
|
||||||
|
this.on();
|
||||||
|
log.info("{}: Seconds until afternoon: {}", this.light.getName(), scheduleBean.nowTillAfternoon());
|
||||||
|
secondsTillOff(scheduleBean.nowTillAfternoon());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void secondsTillOn(long seconds) {
|
||||||
|
secondsTill(seconds, this::on);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void secondsTillOff(long seconds) {
|
||||||
|
secondsTill(seconds, this::off);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void secondsTill(long seconds, Runnable onOff) {
|
||||||
|
var ses = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
ses.schedule(onOff, seconds, TimeUnit.SECONDS);
|
||||||
|
ses.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggle() {
|
||||||
|
log.info("toggling {}", this.light.getName());
|
||||||
|
this.light.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on() {
|
||||||
|
log.info("{}: Turning on!", this.light.getName());
|
||||||
|
this.light.high();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void off() {
|
||||||
|
log.info("{}: Turning off!", this.light.getName());
|
||||||
|
this.light.low();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
62
src/main/java/space/hilltopgrove/lightswitch/model/Rack.java
Normal file
62
src/main/java/space/hilltopgrove/lightswitch/model/Rack.java
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.model;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class Rack {
|
||||||
|
private Map<Integer, Light> lightMap;
|
||||||
|
|
||||||
|
public static Rack map(Map<Integer, Light> lightMap) {
|
||||||
|
return Rack.builder()
|
||||||
|
.lightMap(lightMap)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggle(Integer lightPosition) {
|
||||||
|
if (lightPosition == null) {
|
||||||
|
this.lightMap.values().forEach(Light::toggle);
|
||||||
|
} else {
|
||||||
|
this.lightMap.get(lightPosition).toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on(Integer lightPosition) {
|
||||||
|
if (lightPosition == null) {
|
||||||
|
this.lightMap.values().forEach(Light::on);
|
||||||
|
} else {
|
||||||
|
this.lightMap.get(lightPosition).on();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void off(Integer lightPosition) {
|
||||||
|
if (lightPosition == null) {
|
||||||
|
this.lightMap.values().forEach(Light::off);
|
||||||
|
} else {
|
||||||
|
this.lightMap.get(lightPosition).off();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return lightMap.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRackLights(ScheduleBean scheduleBean) {
|
||||||
|
if (this.size() == 1) {
|
||||||
|
this.lightMap.get(1).setAsSunriseLight(scheduleBean);
|
||||||
|
}
|
||||||
|
if (this.size() == 2) {
|
||||||
|
this.lightMap.get(1).setAsSunriseLight(scheduleBean);
|
||||||
|
this.lightMap.get(2).setAsMorningLight(scheduleBean);
|
||||||
|
}
|
||||||
|
if (this.size() == 3) {
|
||||||
|
this.lightMap.get(2).setAsDawnLight(scheduleBean);
|
||||||
|
this.lightMap.get(1).setAsSunriseLight(scheduleBean);
|
||||||
|
this.lightMap.get(3).setAsMorningLight(scheduleBean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.model;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import space.hilltopgrove.lightswitch.service.impl.SunriseSunset;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
public record ScheduleBean(Instant dawn, Instant sunrise, Instant sunset, Instant dusk) {
|
||||||
|
|
||||||
|
public static ScheduleBean build(Calendar day, double latitude, double longitude) {
|
||||||
|
var sunsetSunrise = SunriseSunset.getSunriseSunset(day, latitude, longitude);
|
||||||
|
var dawnDusk = SunriseSunset.getCivilTwilight(day, latitude, longitude);
|
||||||
|
return ScheduleBean.builder()
|
||||||
|
.dawn(dawnDusk[0].toInstant())
|
||||||
|
.sunrise(sunsetSunrise[0].toInstant())
|
||||||
|
.sunset(sunsetSunrise[1].toInstant())
|
||||||
|
.dusk(dawnDusk[1].toInstant())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNowBeforeDawn() {
|
||||||
|
return Instant.now().isBefore(this.dawn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNowBeforeSunrise() {
|
||||||
|
return Instant.now().isBefore(this.sunrise);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNowBeforeMorning() {
|
||||||
|
return Instant.now().isBefore(this.getMorning());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNowBetweenDawnAndDusk() {
|
||||||
|
var now = Instant.now();
|
||||||
|
return now.isAfter(this.dawn) && now.isBefore(this.dusk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNowBetweenSunriseAndSunset() {
|
||||||
|
var now = Instant.now();
|
||||||
|
return now.isAfter(this.sunrise) && now.isBefore(this.sunset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNowBetweenMorningAndAfternoon() {
|
||||||
|
var now = Instant.now();
|
||||||
|
return now.isAfter(this.getMorning()) && now.isBefore(this.getAfterNoon());
|
||||||
|
}
|
||||||
|
|
||||||
|
public long nowTillDawn() {
|
||||||
|
return nowTill(this.dawn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long nowTillDusk() {
|
||||||
|
return nowTill(this.dusk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long nowTillSunrise() {
|
||||||
|
return nowTill(this.sunrise());
|
||||||
|
}
|
||||||
|
|
||||||
|
public long nowTillSunset() {
|
||||||
|
return nowTill(this.sunset());
|
||||||
|
}
|
||||||
|
|
||||||
|
public long nowTillMorning() {
|
||||||
|
return nowTill(this.getMorning());
|
||||||
|
}
|
||||||
|
|
||||||
|
public long nowTillAfternoon() {
|
||||||
|
return nowTill(this.getAfterNoon());
|
||||||
|
}
|
||||||
|
|
||||||
|
private long nowTill(Instant instant) {
|
||||||
|
return Duration.between(Instant.now(), instant).getSeconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getMorning() {
|
||||||
|
return Instant.ofEpochSecond(sunrise.getEpochSecond() + (getDayLength() / 6 * 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getNoon() {
|
||||||
|
return Instant.ofEpochSecond(sunrise.getEpochSecond() + (getDayLength() / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getAfterNoon() {
|
||||||
|
return Instant.ofEpochSecond(sunrise.getEpochSecond() + (getDayLength() / 6 * 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getEvening() {
|
||||||
|
return Instant.ofEpochSecond(sunrise.getEpochSecond() + (getDayLength() / 6 * 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getDayLength() {
|
||||||
|
return sunset.minusSeconds(sunrise.getEpochSecond()).getEpochSecond();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.model;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class Shelf {
|
||||||
|
private Map<Integer, Rack> rackMap;
|
||||||
|
|
||||||
|
public static Shelf map(Map<Integer, Rack> rackMap) {
|
||||||
|
return Shelf.builder()
|
||||||
|
.rackMap(rackMap)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRacksSchedule(ScheduleBean scheduleBean) {
|
||||||
|
this.rackMap.values().forEach(rack -> {
|
||||||
|
rack.setRackLights(scheduleBean);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggle(Integer rackPosition, Integer lightPosition) {
|
||||||
|
if (rackPosition == null) {
|
||||||
|
this.rackMap.values().forEach(rack -> rack.toggle(lightPosition));
|
||||||
|
} else {
|
||||||
|
this.rackMap.get(rackPosition).toggle(lightPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on(Integer rackPosition, Integer lightPosition) {
|
||||||
|
if (rackPosition == null) {
|
||||||
|
this.rackMap.values().forEach(rack -> rack.on(lightPosition));
|
||||||
|
} else {
|
||||||
|
this.rackMap.get(rackPosition).on(lightPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void off(Integer rackPosition, Integer lightPosition) {
|
||||||
|
if (rackPosition == null) {
|
||||||
|
this.rackMap.values().forEach(rack -> rack.off(lightPosition));
|
||||||
|
} else {
|
||||||
|
this.rackMap.get(rackPosition).off(lightPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.service;
|
||||||
|
|
||||||
|
public interface LightingService {
|
||||||
|
void on(Integer rack, Integer light);
|
||||||
|
void off(Integer rack, Integer light);
|
||||||
|
void toggle(Integer rack, Integer light);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.service;
|
||||||
|
|
||||||
|
import space.hilltopgrove.lightswitch.model.Rack;
|
||||||
|
|
||||||
|
public interface RackLightingPattern {
|
||||||
|
void sunRise(Rack rack);
|
||||||
|
void morning(Rack rack);
|
||||||
|
void noon(Rack rack);
|
||||||
|
void afterNoon(Rack rack);
|
||||||
|
void evening(Rack rack);
|
||||||
|
void sunSet(Rack rack);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.service;
|
||||||
|
|
||||||
|
import space.hilltopgrove.lightswitch.model.ScheduleBean;
|
||||||
|
|
||||||
|
public interface SchedulingService {
|
||||||
|
void setLightingSchedule(ScheduleBean scheduleBean);
|
||||||
|
void setCurrentLighting(ScheduleBean scheduleBean);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.service;
|
||||||
|
|
||||||
|
import space.hilltopgrove.lightswitch.model.ScheduleBean;
|
||||||
|
|
||||||
|
public interface SunRiseSunSetService {
|
||||||
|
ScheduleBean getSchedule();
|
||||||
|
}
|
||||||
@@ -0,0 +1,649 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.service;
|
||||||
|
/*
|
||||||
|
* Sunrise Sunset Calculator.
|
||||||
|
* Copyright (C) 2013-2017 Carmen Alvarez
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides methods to determine the sunrise, sunset, civil twilight,
|
||||||
|
* nautical twilight, and astronomical twilight times of a given
|
||||||
|
* location, or if it is currently day or night at a given location. <br>
|
||||||
|
* Also provides methods to convert between Gregorian and Julian dates.<br>
|
||||||
|
* The formulas used by this class are from the Wikipedia articles on Julian Day
|
||||||
|
* and Sunrise Equation. <br>
|
||||||
|
*
|
||||||
|
* @author Carmen Alvarez
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Julian_day">Julian Day on Wikipedia</a>
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Sunrise_equation">Sunrise equation on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
public final class SunriseSunset {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The altitude of the sun (solar elevation angle) at the moment of sunrise or sunset: -0.833
|
||||||
|
*/
|
||||||
|
public static final double SUN_ALTITUDE_SUNRISE_SUNSET = -0.833;
|
||||||
|
/**
|
||||||
|
* The altitude of the sun (solar elevation angle) at the moment of civil twilight: -6.0
|
||||||
|
*/
|
||||||
|
public static final double SUN_ALTITUDE_CIVIL_TWILIGHT = -6.0;
|
||||||
|
/**
|
||||||
|
* The altitude of the sun (solar elevation angle) at the moment of nautical twilight: -12.0
|
||||||
|
*/
|
||||||
|
public static final double SUN_ALTITUDE_NAUTICAL_TWILIGHT = -12.0;
|
||||||
|
/**
|
||||||
|
* The altitude of the sun (solar elevation angle) at the moment of astronomical twilight: -18.0
|
||||||
|
*/
|
||||||
|
public static final double SUN_ALTITUDE_ASTRONOMICAL_TWILIGHT = -18.0;
|
||||||
|
private static final int JULIAN_DATE_2000_01_01 = 2451545;
|
||||||
|
private static final double CONST_0009 = 0.0009;
|
||||||
|
private static final double CONST_360 = 360;
|
||||||
|
private static final long MILLISECONDS_IN_DAY = 60 * 60 * 24 * 1000;
|
||||||
|
|
||||||
|
private SunriseSunset() {
|
||||||
|
// Prevent instantiation of this utility class
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Gregorian calendar date to a Julian date. Accuracy is to the
|
||||||
|
* second.
|
||||||
|
* <br>
|
||||||
|
* This is based on the Wikipedia article for Julian day.
|
||||||
|
*
|
||||||
|
* @param gregorianDate Gregorian date in any time zone.
|
||||||
|
* @return the Julian date for the given Gregorian date.
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Julian_day#Converting_Julian_or_Gregorian_calendar_date_to_Julian_Day_Number">Converting to Julian day number on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
public static double getJulianDate(final Calendar gregorianDate) {
|
||||||
|
// Convert the date to the UTC time zone.
|
||||||
|
TimeZone tzUTC = TimeZone.getTimeZone("UTC");
|
||||||
|
Calendar gregorianDateUTC = Calendar.getInstance(tzUTC);
|
||||||
|
gregorianDateUTC.setTimeInMillis(gregorianDate.getTimeInMillis());
|
||||||
|
// For the year (Y) astronomical year numbering is used, thus 1 BC is 0,
|
||||||
|
// 2 BC is -1, and 4713 BC is -4712.
|
||||||
|
int year = gregorianDateUTC.get(Calendar.YEAR);
|
||||||
|
// The months (M) January to December are 1 to 12
|
||||||
|
int month = gregorianDateUTC.get(Calendar.MONTH) + 1;
|
||||||
|
// D is the day of the month.
|
||||||
|
int day = gregorianDateUTC.get(Calendar.DAY_OF_MONTH);
|
||||||
|
int a = (14 - month) / 12;
|
||||||
|
int y = year + 4800 - a;
|
||||||
|
int m = month + 12 * a - 3;
|
||||||
|
|
||||||
|
int julianDay = day + (153 * m + 2) / 5 + 365 * y + (y / 4) - (y / 100)
|
||||||
|
+ (y / 400) - 32045;
|
||||||
|
int hour = gregorianDateUTC.get(Calendar.HOUR_OF_DAY);
|
||||||
|
int minute = gregorianDateUTC.get(Calendar.MINUTE);
|
||||||
|
int second = gregorianDateUTC.get(Calendar.SECOND);
|
||||||
|
|
||||||
|
return julianDay + ((double) hour - 12) / 24
|
||||||
|
+ ((double) minute) / 1440 + ((double) second) / 86400;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Julian date to a Gregorian date. The Gregorian date will be in
|
||||||
|
* the local time zone. Accuracy is to the second.
|
||||||
|
* <br>
|
||||||
|
* This is based on the Wikipedia article for Julian day.
|
||||||
|
*
|
||||||
|
* @param julianDate The date to convert
|
||||||
|
* @return a Gregorian date in the local time zone.
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Julian_day#Gregorian_calendar_from_Julian_day_number">Converting from Julian day to Gregorian date, on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
public static Calendar getGregorianDate(final double julianDate) {
|
||||||
|
|
||||||
|
final int DAYS_PER_4000_YEARS = 146097;
|
||||||
|
final int DAYS_PER_CENTURY = 36524;
|
||||||
|
final int DAYS_PER_4_YEARS = 1461;
|
||||||
|
final int DAYS_PER_5_MONTHS = 153;
|
||||||
|
|
||||||
|
// Let J = JD + 0.5: (note: this shifts the epoch back by one half day,
|
||||||
|
// to start it at 00:00UTC, instead of 12:00 UTC);
|
||||||
|
int J = (int) (julianDate + 0.5);
|
||||||
|
|
||||||
|
// let j = J + 32044; (note: this shifts the epoch back to astronomical
|
||||||
|
// year -4800 instead of the start of the Christian era in year AD 1 of
|
||||||
|
// the proleptic Gregorian calendar).
|
||||||
|
int j = J + 32044;
|
||||||
|
|
||||||
|
// let g = j div 146097; let dg = j mod 146097;
|
||||||
|
int g = j / DAYS_PER_4000_YEARS;
|
||||||
|
int dg = j % DAYS_PER_4000_YEARS;
|
||||||
|
|
||||||
|
// let c = (dg div 36524 + 1) * 3 div 4; let dc = dg - c * 36524;
|
||||||
|
int c = ((dg / DAYS_PER_CENTURY + 1) * 3) / 4;
|
||||||
|
int dc = dg - c * DAYS_PER_CENTURY;
|
||||||
|
|
||||||
|
// let b = dc div 1461; let db = dc mod 1461;
|
||||||
|
int b = dc / DAYS_PER_4_YEARS;
|
||||||
|
int db = dc % DAYS_PER_4_YEARS;
|
||||||
|
|
||||||
|
// let a = (db div 365 + 1) * 3 div 4; let da = db - a * 365;
|
||||||
|
int a = ((db / 365 + 1) * 3) / 4;
|
||||||
|
int da = db - a * 365;
|
||||||
|
|
||||||
|
// let y = g * 400 + c * 100 + b * 4 + a; (note: this is the integer
|
||||||
|
// number of full years elapsed since March 1, 4801 BC at 00:00 UTC);
|
||||||
|
int y = g * 400 + c * 100 + b * 4 + a;
|
||||||
|
|
||||||
|
// let m = (da * 5 + 308) div 153 - 2; (note: this is the integer number
|
||||||
|
// of full months elapsed since the last March 1 at 00:00 UTC);
|
||||||
|
int m = (da * 5 + 308) / DAYS_PER_5_MONTHS - 2;
|
||||||
|
|
||||||
|
// let d = da -(m + 4) * 153 div 5 + 122; (note: this is the number of
|
||||||
|
// days elapsed since day 1 of the month at 00:00 UTC, including
|
||||||
|
// fractions of one day);
|
||||||
|
int d = da - ((m + 4) * DAYS_PER_5_MONTHS) / 5 + 122;
|
||||||
|
|
||||||
|
// let Y = y - 4800 + (m + 2) div 12;
|
||||||
|
int year = y - 4800 + (m + 2) / 12;
|
||||||
|
|
||||||
|
// let M = (m + 2) mod 12 + 1;
|
||||||
|
int month = (m + 2) % 12;
|
||||||
|
|
||||||
|
// let D = d + 1;
|
||||||
|
int day = d + 1;
|
||||||
|
|
||||||
|
// Apply the fraction of the day in the Julian date to the Gregorian
|
||||||
|
// date.
|
||||||
|
// Example: dayFraction = 0.717
|
||||||
|
final double dayFraction = (julianDate + 0.5) - J;
|
||||||
|
|
||||||
|
// Ex: 0.717*24 = 17.208 hours. We truncate to 17 hours.
|
||||||
|
final int hours = (int) (dayFraction * 24);
|
||||||
|
// Ex: 17.208 - 17 = 0.208 days. 0.208*60 = 12.48 minutes. We truncate
|
||||||
|
// to 12 minutes.
|
||||||
|
final int minutes = (int) ((dayFraction * 24 - hours) * 60d);
|
||||||
|
// Ex: 17.208*60 - (17*60 + 12) = 1032.48 - 1032 = 0.48 minutes. 0.48*60
|
||||||
|
// = 28.8 seconds.
|
||||||
|
// We round to 29 seconds.
|
||||||
|
final int seconds = (int) ((dayFraction * 24 * 3600 - (hours * 3600 + minutes * 60)) + .5);
|
||||||
|
|
||||||
|
// Create the gregorian date in UTC.
|
||||||
|
final Calendar gregorianDateUTC = Calendar.getInstance(TimeZone
|
||||||
|
.getTimeZone("UTC"));
|
||||||
|
gregorianDateUTC.set(Calendar.YEAR, year);
|
||||||
|
gregorianDateUTC.set(Calendar.MONTH, month);
|
||||||
|
gregorianDateUTC.set(Calendar.DAY_OF_MONTH, day);
|
||||||
|
gregorianDateUTC.set(Calendar.HOUR_OF_DAY, hours);
|
||||||
|
gregorianDateUTC.set(Calendar.MINUTE, minutes);
|
||||||
|
gregorianDateUTC.set(Calendar.SECOND, seconds);
|
||||||
|
gregorianDateUTC.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
// Convert to a Gregorian date in the local time zone.
|
||||||
|
Calendar gregorianDate = Calendar.getInstance();
|
||||||
|
gregorianDate.setTimeInMillis(gregorianDateUTC.getTimeInMillis());
|
||||||
|
return gregorianDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the civil twilight time for the given date and given location.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate civil twilight
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return a two-element Gregorian Calendar array. The first element is the
|
||||||
|
* civil twilight dawn, the second element is the civil twilight dusk.
|
||||||
|
* This will return null if there is no civil twilight. (Ex: no twilight in Antarctica in December)
|
||||||
|
*/
|
||||||
|
public static Calendar[] getCivilTwilight(final Calendar day,
|
||||||
|
final double latitude, double longitude) {
|
||||||
|
return getSunriseSunset(day, latitude, longitude, SUN_ALTITUDE_CIVIL_TWILIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the nautical twilight time for the given date and given location.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate nautical twilight
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return a two-element Gregorian Calendar array. The first element is the
|
||||||
|
* nautical twilight dawn, the second element is the nautical twilight dusk.
|
||||||
|
* This will return null if there is no nautical twilight. (Ex: no twilight in Antarctica in December)
|
||||||
|
*/
|
||||||
|
public static Calendar[] getNauticalTwilight(final Calendar day,
|
||||||
|
final double latitude, double longitude) {
|
||||||
|
return getSunriseSunset(day, latitude, longitude, SUN_ALTITUDE_NAUTICAL_TWILIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the astronomical twilight time for the given date and given location.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate astronomical twilight
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return a two-element Gregorian Calendar array. The first element is the
|
||||||
|
* astronomical twilight dawn, the second element is the astronomical twilight dusk.
|
||||||
|
* This will return null if there is no astronomical twilight. (Ex: no twilight in Antarctica in December)
|
||||||
|
*/
|
||||||
|
public static Calendar[] getAstronomicalTwilight(final Calendar day,
|
||||||
|
final double latitude, double longitude) {
|
||||||
|
return getSunriseSunset(day, latitude, longitude, SUN_ALTITUDE_ASTRONOMICAL_TWILIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the sunrise and sunset times for the given date and given
|
||||||
|
* location. This is based on the Wikipedia article on the Sunrise equation.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate sunrise and sunset
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return a two-element Gregorian Calendar array. The first element is the
|
||||||
|
* sunrise, the second element is the sunset. This will return null if there is no sunrise or sunset. (Ex: no sunrise in Antarctica in June)
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Sunrise_equation">Sunrise equation on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
public static Calendar[] getSunriseSunset(final Calendar day,
|
||||||
|
final double latitude, double longitude) {
|
||||||
|
return getSunriseSunset(day, latitude, longitude, SUN_ALTITUDE_SUNRISE_SUNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return intermediate variables used for calculating sunrise, sunset, and solar noon.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate the ecliptic longitude and jtransit
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return a 2-element array with the ecliptic longitude (lambda) as the first element, and solar transit (jtransit) as the second element
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Sunrise_equation">Sunrise equation on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
private static SolarEquationVariables getSolarEquationVariables(final Calendar day, double longitude) {
|
||||||
|
|
||||||
|
longitude = -longitude;
|
||||||
|
|
||||||
|
// Get the given date as a Julian date.
|
||||||
|
final double julianDate = getJulianDate(day);
|
||||||
|
|
||||||
|
// Calculate current Julian cycle (number of days since 2000-01-01).
|
||||||
|
final double nstar = julianDate - JULIAN_DATE_2000_01_01 - CONST_0009
|
||||||
|
- longitude / CONST_360;
|
||||||
|
final double n = Math.round(nstar);
|
||||||
|
|
||||||
|
// Approximate solar noon
|
||||||
|
final double jstar = JULIAN_DATE_2000_01_01 + CONST_0009 + longitude
|
||||||
|
/ CONST_360 + n;
|
||||||
|
// Solar mean anomaly
|
||||||
|
final double m = Math
|
||||||
|
.toRadians((357.5291 + 0.98560028 * (jstar - JULIAN_DATE_2000_01_01))
|
||||||
|
% CONST_360);
|
||||||
|
|
||||||
|
// Equation of center
|
||||||
|
final double c = 1.9148 * Math.sin(m) + 0.0200 * Math.sin(2 * m)
|
||||||
|
+ 0.0003 * Math.sin(3 * m);
|
||||||
|
|
||||||
|
// Ecliptic longitude
|
||||||
|
final double lambda = Math
|
||||||
|
.toRadians((Math.toDegrees(m) + 102.9372 + c + 180) % CONST_360);
|
||||||
|
|
||||||
|
// Solar transit (hour angle for solar noon)
|
||||||
|
final double jtransit = jstar + 0.0053 * Math.sin(m) - 0.0069
|
||||||
|
* Math.sin(2 * lambda);
|
||||||
|
|
||||||
|
// Declination of the sun.
|
||||||
|
final double delta = Math.asin(Math.sin(lambda)
|
||||||
|
* Math.sin(Math.toRadians(23.439)));
|
||||||
|
|
||||||
|
|
||||||
|
return new SolarEquationVariables(n, m, lambda, jtransit, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the sunrise and sunset times for the given date, given
|
||||||
|
* location, and sun altitude.
|
||||||
|
* This is based on the Wikipedia article on the Sunrise equation.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate sunrise and sunset
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @param sunAltitude <a href="http://en.wikipedia.org/wiki/Solar_zenith_angle#Solar_elevation_angle">the angle between the horizon and the center of the sun's disc.</a>
|
||||||
|
* @return a two-element Gregorian Calendar array. The first element is the
|
||||||
|
* sunrise, the second element is the sunset. This will return null if there is no sunrise or sunset. (Ex: no sunrise in Antarctica in June)
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Sunrise_equation">Sunrise equation on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
public static Calendar[] getSunriseSunset(final Calendar day,
|
||||||
|
final double latitude, double longitude, double sunAltitude) {
|
||||||
|
|
||||||
|
final SolarEquationVariables solarEquationVariables = getSolarEquationVariables(day, longitude);
|
||||||
|
|
||||||
|
longitude = -longitude;
|
||||||
|
final double latitudeRad = Math.toRadians(latitude);
|
||||||
|
|
||||||
|
// Hour angle
|
||||||
|
final double omega = Math.acos((Math.sin(Math.toRadians(sunAltitude)) - Math
|
||||||
|
.sin(latitudeRad) * Math.sin(solarEquationVariables.delta))
|
||||||
|
/ (Math.cos(latitudeRad) * Math.cos(solarEquationVariables.delta)));
|
||||||
|
|
||||||
|
if (Double.isNaN(omega)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sunset
|
||||||
|
final double jset = JULIAN_DATE_2000_01_01
|
||||||
|
+ CONST_0009
|
||||||
|
+ ((Math.toDegrees(omega) + longitude) / CONST_360 + solarEquationVariables.n + 0.0053
|
||||||
|
* Math.sin(solarEquationVariables.m) - 0.0069 * Math.sin(2 * solarEquationVariables.lambda));
|
||||||
|
|
||||||
|
// Sunrise
|
||||||
|
final double jrise = solarEquationVariables.jtransit - (jset - solarEquationVariables.jtransit);
|
||||||
|
// Convert sunset and sunrise to Gregorian dates, in UTC
|
||||||
|
final Calendar gregRiseUTC = getGregorianDate(jrise);
|
||||||
|
final Calendar gregSetUTC = getGregorianDate(jset);
|
||||||
|
|
||||||
|
// Convert the sunset and sunrise to the timezone of the day parameter
|
||||||
|
final Calendar gregRise = Calendar.getInstance(day.getTimeZone());
|
||||||
|
gregRise.setTimeInMillis(gregRiseUTC.getTimeInMillis());
|
||||||
|
final Calendar gregSet = Calendar.getInstance(day.getTimeZone());
|
||||||
|
gregSet.setTimeInMillis(gregSetUTC.getTimeInMillis());
|
||||||
|
return new Calendar[]{gregRise, gregSet};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the solar noon time for the given date and given location.
|
||||||
|
* This is based on the Wikipedia article on the Sunrise equation.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate sunrise and sunset
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return a Calendar with the time set to solar noon for the given day.
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Sunrise_equation">Sunrise equation on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
public static Calendar getSolarNoon(final Calendar day, final double latitude, double longitude) {
|
||||||
|
SolarEquationVariables solarEquationVariables = getSolarEquationVariables(day, longitude);
|
||||||
|
|
||||||
|
// Add a check for Antarctica in June and December (sun always down or up, respectively).
|
||||||
|
// In this case, jtransit will be filled in, but we need to check the hour angle omega for
|
||||||
|
// sunrise.
|
||||||
|
// If there's no sunrise (omega is NaN), there's no solar noon.
|
||||||
|
final double latitudeRad = Math.toRadians(latitude);
|
||||||
|
|
||||||
|
// Hour angle
|
||||||
|
final double omega = Math.acos((Math.sin(Math.toRadians(SUN_ALTITUDE_SUNRISE_SUNSET)) - Math
|
||||||
|
.sin(latitudeRad) * Math.sin(solarEquationVariables.delta))
|
||||||
|
/ (Math.cos(latitudeRad) * Math.cos(solarEquationVariables.delta)));
|
||||||
|
|
||||||
|
if (Double.isNaN(omega)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert jtransit Gregorian dates, in UTC
|
||||||
|
final Calendar gregNoonUTC = getGregorianDate(solarEquationVariables.jtransit);
|
||||||
|
final Calendar gregNoon = Calendar.getInstance(day.getTimeZone());
|
||||||
|
gregNoon.setTimeInMillis(gregNoonUTC.getTimeInMillis());
|
||||||
|
return gregNoon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is currently day at the given location. This returns
|
||||||
|
* true if the current time at the location is after the sunrise and
|
||||||
|
* before the sunset for that location.
|
||||||
|
*/
|
||||||
|
public static boolean isDay(double latitude, double longitude) {
|
||||||
|
Calendar now = Calendar.getInstance();
|
||||||
|
return isDay(now, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calendar a datetime
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is day at the given location and given datetime. This returns
|
||||||
|
* true if the given datetime at the location is after the sunrise and
|
||||||
|
* before the sunset for that location.
|
||||||
|
*/
|
||||||
|
public static boolean isDay(Calendar calendar, double latitude, double longitude) {
|
||||||
|
Calendar[] sunriseSunset = getSunriseSunset(calendar, latitude, longitude);
|
||||||
|
// In extreme latitudes, there may be no sunrise/sunset time in summer or
|
||||||
|
// winter, because it will be day or night 24 hours
|
||||||
|
if (sunriseSunset == null) {
|
||||||
|
int month = calendar.get(Calendar.MONTH); // Reminder: January = 0
|
||||||
|
if (latitude > 0) {
|
||||||
|
// Always night at the north pole in December
|
||||||
|
return month >= 3 && month <= 10; // Always day at the north pole in June
|
||||||
|
} else {
|
||||||
|
// Always day at the south pole in December
|
||||||
|
return month < 3 || month > 10; // Always night at the south pole in June
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Calendar sunrise = sunriseSunset[0];
|
||||||
|
Calendar sunset = sunriseSunset[1];
|
||||||
|
return calendar.after(sunrise) && calendar.before(sunset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is night at the given location currently. This returns
|
||||||
|
* true if the current time at the location is after the astronomical twilight dusk and
|
||||||
|
* before the astronomical twilight dawn for that location.
|
||||||
|
*/
|
||||||
|
public static boolean isNight(double latitude, double longitude) {
|
||||||
|
Calendar now = Calendar.getInstance();
|
||||||
|
return isNight(now, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calendar a datetime
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is night at the given location and datetime. This returns
|
||||||
|
* true if the given datetime at the location is after the astronomical twilight dusk and before
|
||||||
|
* the astronomical twilight dawn.
|
||||||
|
*/
|
||||||
|
public static boolean isNight(Calendar calendar, double latitude, double longitude) {
|
||||||
|
Calendar[] astronomicalTwilight = getAstronomicalTwilight(calendar, latitude, longitude);
|
||||||
|
if (astronomicalTwilight == null) {
|
||||||
|
int month = calendar.get(Calendar.MONTH); // Reminder: January = 0
|
||||||
|
if (latitude > 0) {
|
||||||
|
// Always night at the north pole in December
|
||||||
|
return month < 3 || month > 10; // Always day at the north pole in June
|
||||||
|
} else {
|
||||||
|
// Always day at the south pole in December
|
||||||
|
return month >= 3 && month <= 10; // Always night at the south pole in June
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Calendar dawn = astronomicalTwilight[0];
|
||||||
|
Calendar dusk = astronomicalTwilight[1];
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss z");
|
||||||
|
format.setTimeZone(calendar.getTimeZone());
|
||||||
|
return calendar.before(dawn) || calendar.after(dusk);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is currently civil twilight at the current time at the given location.
|
||||||
|
* This returns true if the current time at the location is between sunset and civil twilight dusk
|
||||||
|
* or between civil twilight dawn and sunrise.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static boolean isCivilTwilight(double latitude, double longitude) {
|
||||||
|
Calendar today = Calendar.getInstance();
|
||||||
|
return isCivilTwilight(today, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calendar the datetime for which to determine if it's civil twilight in the given location
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is civil twilight at the given location and the given calendar.
|
||||||
|
* This returns true if the given time at the location is between sunset and civil twilight dusk
|
||||||
|
* or between civil twilight dawn and sunrise.
|
||||||
|
*/
|
||||||
|
public static boolean isCivilTwilight(Calendar calendar, double latitude, double longitude) {
|
||||||
|
Calendar[] sunriseSunset = getSunriseSunset(calendar, latitude, longitude);
|
||||||
|
if (sunriseSunset == null) return false;
|
||||||
|
Calendar[] civilTwilight = getCivilTwilight(calendar, latitude, longitude);
|
||||||
|
if (civilTwilight == null) return false;
|
||||||
|
|
||||||
|
return (calendar.after(sunriseSunset[1]) && calendar.before(civilTwilight[1])
|
||||||
|
|| (calendar.after(civilTwilight[0]) && calendar.before(sunriseSunset[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is currently nautical twilight at the current time at the given location.
|
||||||
|
* This returns true if the current time at the location is between civil and nautical twilight dusk
|
||||||
|
* or between nautical and civil twilight dawn.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static boolean isNauticalTwilight(double latitude, double longitude) {
|
||||||
|
Calendar today = Calendar.getInstance();
|
||||||
|
return isNauticalTwilight(today, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calendar the datetime for which to determine if it's nautical twilight in the given location
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is nautical twilight at the given location and the given calendar.
|
||||||
|
* This returns true if the given time at the location is between civil and nautical twilight dusk
|
||||||
|
* or between nautical and civil twilight dawn.
|
||||||
|
*/
|
||||||
|
public static boolean isNauticalTwilight(Calendar calendar, double latitude, double longitude) {
|
||||||
|
Calendar[] civilTwilight = getCivilTwilight(calendar, latitude, longitude);
|
||||||
|
if (civilTwilight == null) return false;
|
||||||
|
Calendar[] nauticalTwilight = getNauticalTwilight(calendar, latitude, longitude);
|
||||||
|
if (nauticalTwilight == null) return false;
|
||||||
|
|
||||||
|
return (calendar.after(civilTwilight[1]) && calendar.before(nauticalTwilight[1])
|
||||||
|
|| (calendar.after(nauticalTwilight[0]) && calendar.before(civilTwilight[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is currently astronomical twilight at the current time at the given location.
|
||||||
|
* This returns true if the current time at the location is between nautical and astronomical twilight dusk
|
||||||
|
* or between astronomical and nautical twilight dawn.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static boolean isAstronomicalTwilight(double latitude, double longitude) {
|
||||||
|
Calendar today = Calendar.getInstance();
|
||||||
|
return isAstronomicalTwilight(today, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calendar the datetime for which to determine if it's astronomical twilight in the given location
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is astronomical twilight at the given location and the given calendar.
|
||||||
|
* This returns true if the given time at the location is between nautical and astronomical twilight dusk
|
||||||
|
* or between astronomical and nautical twilight dawn.
|
||||||
|
*/
|
||||||
|
public static boolean isAstronomicalTwilight(Calendar calendar, double latitude, double longitude) {
|
||||||
|
Calendar[] nauticalTwilight = getNauticalTwilight(calendar, latitude, longitude);
|
||||||
|
if (nauticalTwilight == null) return false;
|
||||||
|
Calendar[] astronomicalTwilight = getAstronomicalTwilight(calendar, latitude, longitude);
|
||||||
|
if (astronomicalTwilight == null) return false;
|
||||||
|
|
||||||
|
return (calendar.after(nauticalTwilight[1]) && calendar.before(astronomicalTwilight[1])
|
||||||
|
|| (calendar.after(astronomicalTwilight[0]) && calendar.before(nauticalTwilight[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is civil, nautical, or astronomical twilight currently at the given location.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static boolean isTwilight(double latitude, double longitude) {
|
||||||
|
Calendar today = Calendar.getInstance();
|
||||||
|
return isTwilight(today, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @param calendar the given datetime to check for twilight
|
||||||
|
* @return true if at the given location and calendar, it is civil, nautical, or astronomical twilight.
|
||||||
|
*/
|
||||||
|
public static boolean isTwilight(Calendar calendar, double latitude, double longitude) {
|
||||||
|
return isCivilTwilight(calendar, latitude, longitude)
|
||||||
|
|| isNauticalTwilight(calendar, latitude, longitude)
|
||||||
|
|| isAstronomicalTwilight(calendar, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DayPeriod getDayPeriod(Calendar calendar, double latitude, double longitude) {
|
||||||
|
if (isDay(calendar, latitude, longitude)) return DayPeriod.DAY;
|
||||||
|
if (isCivilTwilight(calendar, latitude, longitude)) return DayPeriod.CIVIL_TWILIGHT;
|
||||||
|
if (isNauticalTwilight(calendar, latitude, longitude)) return DayPeriod.NAUTICAL_TWILIGHT;
|
||||||
|
if (isAstronomicalTwilight(calendar, latitude, longitude)) return DayPeriod.ASTRONOMICAL_TWILIGHT;
|
||||||
|
if (isNight(calendar, latitude, longitude)) return DayPeriod.NIGHT;
|
||||||
|
return DayPeriod.NIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calendar the datetime for which to determine the day length
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return the number of milliseconds between sunrise and sunset.
|
||||||
|
*/
|
||||||
|
public static long getDayLength(Calendar calendar, double latitude, double longitude) {
|
||||||
|
Calendar[] sunriseSunset = getSunriseSunset(calendar, latitude, longitude);
|
||||||
|
if (sunriseSunset == null) {
|
||||||
|
int month = calendar.get(Calendar.MONTH); // Reminder: January = 0
|
||||||
|
if (latitude > 0) {
|
||||||
|
if (month >= 3 && month <= 10) {
|
||||||
|
return MILLISECONDS_IN_DAY; // Always day at the north pole in June
|
||||||
|
} else {
|
||||||
|
return 0; // Always night at the north pole in December
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (month >= 3 && month <= 10) {
|
||||||
|
return 0; // Always night at the south pole in June
|
||||||
|
} else {
|
||||||
|
return MILLISECONDS_IN_DAY; // Always day at the south pole in December
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sunriseSunset[1].getTimeInMillis() - sunriseSunset[0].getTimeInMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DayPeriod {
|
||||||
|
DAY,
|
||||||
|
CIVIL_TWILIGHT,
|
||||||
|
NAUTICAL_TWILIGHT,
|
||||||
|
ASTRONOMICAL_TWILIGHT,
|
||||||
|
NIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intermediate variables used in the sunrise equation
|
||||||
|
*
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Sunrise_equation">Sunrise equation on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
private static class SolarEquationVariables {
|
||||||
|
final double n;// Julian cycle (number of days since 2000-01-01).
|
||||||
|
final double m; // solar mean anomaly
|
||||||
|
final double lambda; // ecliptic longitude
|
||||||
|
final double jtransit; // Solar transit (hour angle for solar noon)
|
||||||
|
final double delta; // Declination of the sun
|
||||||
|
|
||||||
|
private SolarEquationVariables(double n, double m, double lambda, double jtransit, double delta) {
|
||||||
|
this.n = n;
|
||||||
|
this.m = m;
|
||||||
|
this.lambda = lambda;
|
||||||
|
this.jtransit = jtransit;
|
||||||
|
this.delta = delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.service.impl;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import space.hilltopgrove.lightswitch.model.Shelf;
|
||||||
|
import space.hilltopgrove.lightswitch.service.LightingService;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LightingServiceImpl implements LightingService {
|
||||||
|
|
||||||
|
private final Shelf shelf;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void on(Integer rack, Integer light) {
|
||||||
|
shelf.on(rack, light);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void off(Integer rack, Integer light) {
|
||||||
|
shelf.off(rack, light);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toggle(Integer rack, Integer light) {
|
||||||
|
shelf.toggle(rack, light);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.service.impl;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import space.hilltopgrove.lightswitch.model.Rack;
|
||||||
|
import space.hilltopgrove.lightswitch.service.RackLightingPattern;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class RackLightingPatternImpl implements RackLightingPattern {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sunRise(Rack rack) {
|
||||||
|
if (rack.size() == 2) {
|
||||||
|
rack.off(1);
|
||||||
|
rack.off(2);
|
||||||
|
}
|
||||||
|
if (rack.size() == 3) {
|
||||||
|
rack.off(1);
|
||||||
|
rack.on(2);
|
||||||
|
rack.off(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void morning(Rack rack) {
|
||||||
|
if (rack.size() == 2) {
|
||||||
|
rack.on(1);
|
||||||
|
rack.off(2);
|
||||||
|
}
|
||||||
|
if (rack.size() == 3) {
|
||||||
|
rack.on(1);
|
||||||
|
rack.on(2);
|
||||||
|
rack.off(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noon(Rack rack) {
|
||||||
|
rack.on(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterNoon(Rack rack) {
|
||||||
|
if (rack.size() == 2) {
|
||||||
|
rack.off(1);
|
||||||
|
rack.on(2);
|
||||||
|
}
|
||||||
|
if (rack.size() == 3) {
|
||||||
|
rack.off(1);
|
||||||
|
rack.on(2);
|
||||||
|
rack.on(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void evening(Rack rack) {
|
||||||
|
if (rack.size() == 2) {
|
||||||
|
rack.off(1);
|
||||||
|
rack.off(2);
|
||||||
|
}
|
||||||
|
if (rack.size() == 3) {
|
||||||
|
rack.off(1);
|
||||||
|
rack.on(2);
|
||||||
|
rack.off(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sunSet(Rack rack) {
|
||||||
|
rack.off(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.service.impl;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import space.hilltopgrove.lightswitch.model.ScheduleBean;
|
||||||
|
import space.hilltopgrove.lightswitch.model.Shelf;
|
||||||
|
import space.hilltopgrove.lightswitch.service.RackLightingPattern;
|
||||||
|
import space.hilltopgrove.lightswitch.service.SchedulingService;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SchedulingServiceImpl implements SchedulingService {
|
||||||
|
|
||||||
|
private final RackLightingPattern rackLightingPattern;
|
||||||
|
private final Shelf shelf;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLightingSchedule(ScheduleBean scheduleBean) {
|
||||||
|
var now = Instant.now();
|
||||||
|
var ses = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
log.info("setLightingSchedule:: now: " + now);
|
||||||
|
log.info("setLightingSchedule:: scheduleBean: " + scheduleBean);
|
||||||
|
for (var rack : shelf.getRackMap().values()) {
|
||||||
|
if (now.isBefore(scheduleBean.sunrise())) {
|
||||||
|
log.info("setLightingSchedule:: sunrise: " + scheduleBean.sunrise());
|
||||||
|
ses.schedule(() -> rackLightingPattern.sunRise(rack), Duration.between(now, scheduleBean.sunrise()).getSeconds(), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
if (now.isBefore(scheduleBean.getMorning())) {
|
||||||
|
log.info("setLightingSchedule:: morning: " + scheduleBean.getMorning());
|
||||||
|
ses.schedule(() -> rackLightingPattern.morning(rack), Duration.between(now, scheduleBean.getMorning()).getSeconds(), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
if (now.isBefore(scheduleBean.getNoon())) {
|
||||||
|
log.info("setLightingSchedule:: noon: " + scheduleBean.getNoon());
|
||||||
|
ses.schedule(() -> rackLightingPattern.noon(rack), Duration.between(now, scheduleBean.getNoon()).getSeconds(), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
if (now.isBefore(scheduleBean.getAfterNoon())) {
|
||||||
|
log.info("setLightingSchedule:: afterNoon: " + scheduleBean.getAfterNoon());
|
||||||
|
ses.schedule(() -> rackLightingPattern.afterNoon(rack), Duration.between(now, scheduleBean.getAfterNoon()).getSeconds(), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
if (now.isBefore(scheduleBean.getEvening())) {
|
||||||
|
log.info("setLightingSchedule:: evening: " + scheduleBean.getEvening());
|
||||||
|
ses.schedule(() -> rackLightingPattern.evening(rack), Duration.between(now, scheduleBean.getEvening()).getSeconds(), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
if (now.isBefore(scheduleBean.sunset())) {
|
||||||
|
log.info("setLightingSchedule:: sunset: " + scheduleBean.sunset());
|
||||||
|
ses.schedule(() -> rackLightingPattern.sunSet(rack), Duration.between(now, scheduleBean.sunset()).getSeconds(), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ses.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCurrentLighting(ScheduleBean scheduleBean) {
|
||||||
|
var now = Instant.now();
|
||||||
|
log.info("setCurrentLighting:: now: " + now);
|
||||||
|
log.info("setCurrentLighting:: scheduleBean: " + scheduleBean);
|
||||||
|
shelf.getRackMap().values().forEach(rack -> {
|
||||||
|
if (now.isAfter(scheduleBean.sunset())) {
|
||||||
|
log.info("setCurrentLighting:: sunset: " + scheduleBean.sunset());
|
||||||
|
rackLightingPattern.sunSet(rack);
|
||||||
|
} else if (now.isAfter(scheduleBean.getEvening())) {
|
||||||
|
log.info("setCurrentLighting:: evening: " + scheduleBean.getEvening());
|
||||||
|
rackLightingPattern.evening(rack);
|
||||||
|
} else if (now.isAfter(scheduleBean.getAfterNoon())) {
|
||||||
|
log.info("setCurrentLighting:: afternoon: " + scheduleBean.getAfterNoon());
|
||||||
|
rackLightingPattern.afterNoon(rack);
|
||||||
|
} else if (now.isAfter(scheduleBean.getNoon())) {
|
||||||
|
log.info("setCurrentLighting:: noon: " + scheduleBean.getNoon());
|
||||||
|
rackLightingPattern.noon(rack);
|
||||||
|
} else if (now.isAfter(scheduleBean.getMorning())) {
|
||||||
|
log.info("setCurrentLighting:: morning: " + scheduleBean.getMorning());
|
||||||
|
rackLightingPattern.morning(rack);
|
||||||
|
} else {
|
||||||
|
log.info("setCurrentLighting:: sunrise: " + scheduleBean.sunrise());
|
||||||
|
rackLightingPattern.sunRise(rack);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
log.info("setCurrentLighting:: now: " + Instant.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.service.impl;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import space.hilltopgrove.lightswitch.config.PropertiesConfig;
|
||||||
|
import space.hilltopgrove.lightswitch.model.ScheduleBean;
|
||||||
|
import space.hilltopgrove.lightswitch.service.SunRiseSunSetService;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SunRiseSunSetServiceImpl implements SunRiseSunSetService {
|
||||||
|
|
||||||
|
private final PropertiesConfig propertiesConfig;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduleBean getSchedule() {
|
||||||
|
var result = ScheduleBean.build(Calendar.getInstance(), propertiesConfig.getLatitude(), propertiesConfig.getLongitude());
|
||||||
|
log.info(result.toString());
|
||||||
|
log.info(Instant.now().toString());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,649 @@
|
|||||||
|
package space.hilltopgrove.lightswitch.service.impl;
|
||||||
|
/*
|
||||||
|
* Sunrise Sunset Calculator.
|
||||||
|
* Copyright (C) 2013-2017 Carmen Alvarez
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides methods to determine the sunrise, sunset, civil twilight,
|
||||||
|
* nautical twilight, and astronomical twilight times of a given
|
||||||
|
* location, or if it is currently day or night at a given location. <br>
|
||||||
|
* Also provides methods to convert between Gregorian and Julian dates.<br>
|
||||||
|
* The formulas used by this class are from the Wikipedia articles on Julian Day
|
||||||
|
* and Sunrise Equation. <br>
|
||||||
|
*
|
||||||
|
* @author Carmen Alvarez
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Julian_day">Julian Day on Wikipedia</a>
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Sunrise_equation">Sunrise equation on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
public final class SunriseSunset {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The altitude of the sun (solar elevation angle) at the moment of sunrise or sunset: -0.833
|
||||||
|
*/
|
||||||
|
public static final double SUN_ALTITUDE_SUNRISE_SUNSET = -0.833;
|
||||||
|
/**
|
||||||
|
* The altitude of the sun (solar elevation angle) at the moment of civil twilight: -6.0
|
||||||
|
*/
|
||||||
|
public static final double SUN_ALTITUDE_CIVIL_TWILIGHT = -6.0;
|
||||||
|
/**
|
||||||
|
* The altitude of the sun (solar elevation angle) at the moment of nautical twilight: -12.0
|
||||||
|
*/
|
||||||
|
public static final double SUN_ALTITUDE_NAUTICAL_TWILIGHT = -12.0;
|
||||||
|
/**
|
||||||
|
* The altitude of the sun (solar elevation angle) at the moment of astronomical twilight: -18.0
|
||||||
|
*/
|
||||||
|
public static final double SUN_ALTITUDE_ASTRONOMICAL_TWILIGHT = -18.0;
|
||||||
|
private static final int JULIAN_DATE_2000_01_01 = 2451545;
|
||||||
|
private static final double CONST_0009 = 0.0009;
|
||||||
|
private static final double CONST_360 = 360;
|
||||||
|
private static final long MILLISECONDS_IN_DAY = 60 * 60 * 24 * 1000;
|
||||||
|
|
||||||
|
private SunriseSunset() {
|
||||||
|
// Prevent instantiation of this utility class
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Gregorian calendar date to a Julian date. Accuracy is to the
|
||||||
|
* second.
|
||||||
|
* <br>
|
||||||
|
* This is based on the Wikipedia article for Julian day.
|
||||||
|
*
|
||||||
|
* @param gregorianDate Gregorian date in any time zone.
|
||||||
|
* @return the Julian date for the given Gregorian date.
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Julian_day#Converting_Julian_or_Gregorian_calendar_date_to_Julian_Day_Number">Converting to Julian day number on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
public static double getJulianDate(final Calendar gregorianDate) {
|
||||||
|
// Convert the date to the UTC time zone.
|
||||||
|
TimeZone tzUTC = TimeZone.getTimeZone("UTC");
|
||||||
|
Calendar gregorianDateUTC = Calendar.getInstance(tzUTC);
|
||||||
|
gregorianDateUTC.setTimeInMillis(gregorianDate.getTimeInMillis());
|
||||||
|
// For the year (Y) astronomical year numbering is used, thus 1 BC is 0,
|
||||||
|
// 2 BC is -1, and 4713 BC is -4712.
|
||||||
|
int year = gregorianDateUTC.get(Calendar.YEAR);
|
||||||
|
// The months (M) January to December are 1 to 12
|
||||||
|
int month = gregorianDateUTC.get(Calendar.MONTH) + 1;
|
||||||
|
// D is the day of the month.
|
||||||
|
int day = gregorianDateUTC.get(Calendar.DAY_OF_MONTH);
|
||||||
|
int a = (14 - month) / 12;
|
||||||
|
int y = year + 4800 - a;
|
||||||
|
int m = month + 12 * a - 3;
|
||||||
|
|
||||||
|
int julianDay = day + (153 * m + 2) / 5 + 365 * y + (y / 4) - (y / 100)
|
||||||
|
+ (y / 400) - 32045;
|
||||||
|
int hour = gregorianDateUTC.get(Calendar.HOUR_OF_DAY);
|
||||||
|
int minute = gregorianDateUTC.get(Calendar.MINUTE);
|
||||||
|
int second = gregorianDateUTC.get(Calendar.SECOND);
|
||||||
|
|
||||||
|
return julianDay + ((double) hour - 12) / 24
|
||||||
|
+ ((double) minute) / 1440 + ((double) second) / 86400;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Julian date to a Gregorian date. The Gregorian date will be in
|
||||||
|
* the local time zone. Accuracy is to the second.
|
||||||
|
* <br>
|
||||||
|
* This is based on the Wikipedia article for Julian day.
|
||||||
|
*
|
||||||
|
* @param julianDate The date to convert
|
||||||
|
* @return a Gregorian date in the local time zone.
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Julian_day#Gregorian_calendar_from_Julian_day_number">Converting from Julian day to Gregorian date, on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
public static Calendar getGregorianDate(final double julianDate) {
|
||||||
|
|
||||||
|
final int DAYS_PER_4000_YEARS = 146097;
|
||||||
|
final int DAYS_PER_CENTURY = 36524;
|
||||||
|
final int DAYS_PER_4_YEARS = 1461;
|
||||||
|
final int DAYS_PER_5_MONTHS = 153;
|
||||||
|
|
||||||
|
// Let J = JD + 0.5: (note: this shifts the epoch back by one half day,
|
||||||
|
// to start it at 00:00UTC, instead of 12:00 UTC);
|
||||||
|
int J = (int) (julianDate + 0.5);
|
||||||
|
|
||||||
|
// let j = J + 32044; (note: this shifts the epoch back to astronomical
|
||||||
|
// year -4800 instead of the start of the Christian era in year AD 1 of
|
||||||
|
// the proleptic Gregorian calendar).
|
||||||
|
int j = J + 32044;
|
||||||
|
|
||||||
|
// let g = j div 146097; let dg = j mod 146097;
|
||||||
|
int g = j / DAYS_PER_4000_YEARS;
|
||||||
|
int dg = j % DAYS_PER_4000_YEARS;
|
||||||
|
|
||||||
|
// let c = (dg div 36524 + 1) * 3 div 4; let dc = dg - c * 36524;
|
||||||
|
int c = ((dg / DAYS_PER_CENTURY + 1) * 3) / 4;
|
||||||
|
int dc = dg - c * DAYS_PER_CENTURY;
|
||||||
|
|
||||||
|
// let b = dc div 1461; let db = dc mod 1461;
|
||||||
|
int b = dc / DAYS_PER_4_YEARS;
|
||||||
|
int db = dc % DAYS_PER_4_YEARS;
|
||||||
|
|
||||||
|
// let a = (db div 365 + 1) * 3 div 4; let da = db - a * 365;
|
||||||
|
int a = ((db / 365 + 1) * 3) / 4;
|
||||||
|
int da = db - a * 365;
|
||||||
|
|
||||||
|
// let y = g * 400 + c * 100 + b * 4 + a; (note: this is the integer
|
||||||
|
// number of full years elapsed since March 1, 4801 BC at 00:00 UTC);
|
||||||
|
int y = g * 400 + c * 100 + b * 4 + a;
|
||||||
|
|
||||||
|
// let m = (da * 5 + 308) div 153 - 2; (note: this is the integer number
|
||||||
|
// of full months elapsed since the last March 1 at 00:00 UTC);
|
||||||
|
int m = (da * 5 + 308) / DAYS_PER_5_MONTHS - 2;
|
||||||
|
|
||||||
|
// let d = da -(m + 4) * 153 div 5 + 122; (note: this is the number of
|
||||||
|
// days elapsed since day 1 of the month at 00:00 UTC, including
|
||||||
|
// fractions of one day);
|
||||||
|
int d = da - ((m + 4) * DAYS_PER_5_MONTHS) / 5 + 122;
|
||||||
|
|
||||||
|
// let Y = y - 4800 + (m + 2) div 12;
|
||||||
|
int year = y - 4800 + (m + 2) / 12;
|
||||||
|
|
||||||
|
// let M = (m + 2) mod 12 + 1;
|
||||||
|
int month = (m + 2) % 12;
|
||||||
|
|
||||||
|
// let D = d + 1;
|
||||||
|
int day = d + 1;
|
||||||
|
|
||||||
|
// Apply the fraction of the day in the Julian date to the Gregorian
|
||||||
|
// date.
|
||||||
|
// Example: dayFraction = 0.717
|
||||||
|
final double dayFraction = (julianDate + 0.5) - J;
|
||||||
|
|
||||||
|
// Ex: 0.717*24 = 17.208 hours. We truncate to 17 hours.
|
||||||
|
final int hours = (int) (dayFraction * 24);
|
||||||
|
// Ex: 17.208 - 17 = 0.208 days. 0.208*60 = 12.48 minutes. We truncate
|
||||||
|
// to 12 minutes.
|
||||||
|
final int minutes = (int) ((dayFraction * 24 - hours) * 60d);
|
||||||
|
// Ex: 17.208*60 - (17*60 + 12) = 1032.48 - 1032 = 0.48 minutes. 0.48*60
|
||||||
|
// = 28.8 seconds.
|
||||||
|
// We round to 29 seconds.
|
||||||
|
final int seconds = (int) ((dayFraction * 24 * 3600 - (hours * 3600 + minutes * 60)) + .5);
|
||||||
|
|
||||||
|
// Create the gregorian date in UTC.
|
||||||
|
final Calendar gregorianDateUTC = Calendar.getInstance(TimeZone
|
||||||
|
.getTimeZone("UTC"));
|
||||||
|
gregorianDateUTC.set(Calendar.YEAR, year);
|
||||||
|
gregorianDateUTC.set(Calendar.MONTH, month);
|
||||||
|
gregorianDateUTC.set(Calendar.DAY_OF_MONTH, day);
|
||||||
|
gregorianDateUTC.set(Calendar.HOUR_OF_DAY, hours);
|
||||||
|
gregorianDateUTC.set(Calendar.MINUTE, minutes);
|
||||||
|
gregorianDateUTC.set(Calendar.SECOND, seconds);
|
||||||
|
gregorianDateUTC.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
// Convert to a Gregorian date in the local time zone.
|
||||||
|
Calendar gregorianDate = Calendar.getInstance();
|
||||||
|
gregorianDate.setTimeInMillis(gregorianDateUTC.getTimeInMillis());
|
||||||
|
return gregorianDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the civil twilight time for the given date and given location.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate civil twilight
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return a two-element Gregorian Calendar array. The first element is the
|
||||||
|
* civil twilight dawn, the second element is the civil twilight dusk.
|
||||||
|
* This will return null if there is no civil twilight. (Ex: no twilight in Antarctica in December)
|
||||||
|
*/
|
||||||
|
public static Calendar[] getCivilTwilight(final Calendar day,
|
||||||
|
final double latitude, double longitude) {
|
||||||
|
return getSunriseSunset(day, latitude, longitude, SUN_ALTITUDE_CIVIL_TWILIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the nautical twilight time for the given date and given location.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate nautical twilight
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return a two-element Gregorian Calendar array. The first element is the
|
||||||
|
* nautical twilight dawn, the second element is the nautical twilight dusk.
|
||||||
|
* This will return null if there is no nautical twilight. (Ex: no twilight in Antarctica in December)
|
||||||
|
*/
|
||||||
|
public static Calendar[] getNauticalTwilight(final Calendar day,
|
||||||
|
final double latitude, double longitude) {
|
||||||
|
return getSunriseSunset(day, latitude, longitude, SUN_ALTITUDE_NAUTICAL_TWILIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the astronomical twilight time for the given date and given location.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate astronomical twilight
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return a two-element Gregorian Calendar array. The first element is the
|
||||||
|
* astronomical twilight dawn, the second element is the astronomical twilight dusk.
|
||||||
|
* This will return null if there is no astronomical twilight. (Ex: no twilight in Antarctica in December)
|
||||||
|
*/
|
||||||
|
public static Calendar[] getAstronomicalTwilight(final Calendar day,
|
||||||
|
final double latitude, double longitude) {
|
||||||
|
return getSunriseSunset(day, latitude, longitude, SUN_ALTITUDE_ASTRONOMICAL_TWILIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the sunrise and sunset times for the given date and given
|
||||||
|
* location. This is based on the Wikipedia article on the Sunrise equation.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate sunrise and sunset
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return a two-element Gregorian Calendar array. The first element is the
|
||||||
|
* sunrise, the second element is the sunset. This will return null if there is no sunrise or sunset. (Ex: no sunrise in Antarctica in June)
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Sunrise_equation">Sunrise equation on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
public static Calendar[] getSunriseSunset(final Calendar day,
|
||||||
|
final double latitude, double longitude) {
|
||||||
|
return getSunriseSunset(day, latitude, longitude, SUN_ALTITUDE_SUNRISE_SUNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return intermediate variables used for calculating sunrise, sunset, and solar noon.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate the ecliptic longitude and jtransit
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return a 2-element array with the ecliptic longitude (lambda) as the first element, and solar transit (jtransit) as the second element
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Sunrise_equation">Sunrise equation on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
private static SolarEquationVariables getSolarEquationVariables(final Calendar day, double longitude) {
|
||||||
|
|
||||||
|
longitude = -longitude;
|
||||||
|
|
||||||
|
// Get the given date as a Julian date.
|
||||||
|
final double julianDate = getJulianDate(day);
|
||||||
|
|
||||||
|
// Calculate current Julian cycle (number of days since 2000-01-01).
|
||||||
|
final double nstar = julianDate - JULIAN_DATE_2000_01_01 - CONST_0009
|
||||||
|
- longitude / CONST_360;
|
||||||
|
final double n = Math.round(nstar);
|
||||||
|
|
||||||
|
// Approximate solar noon
|
||||||
|
final double jstar = JULIAN_DATE_2000_01_01 + CONST_0009 + longitude
|
||||||
|
/ CONST_360 + n;
|
||||||
|
// Solar mean anomaly
|
||||||
|
final double m = Math
|
||||||
|
.toRadians((357.5291 + 0.98560028 * (jstar - JULIAN_DATE_2000_01_01))
|
||||||
|
% CONST_360);
|
||||||
|
|
||||||
|
// Equation of center
|
||||||
|
final double c = 1.9148 * Math.sin(m) + 0.0200 * Math.sin(2 * m)
|
||||||
|
+ 0.0003 * Math.sin(3 * m);
|
||||||
|
|
||||||
|
// Ecliptic longitude
|
||||||
|
final double lambda = Math
|
||||||
|
.toRadians((Math.toDegrees(m) + 102.9372 + c + 180) % CONST_360);
|
||||||
|
|
||||||
|
// Solar transit (hour angle for solar noon)
|
||||||
|
final double jtransit = jstar + 0.0053 * Math.sin(m) - 0.0069
|
||||||
|
* Math.sin(2 * lambda);
|
||||||
|
|
||||||
|
// Declination of the sun.
|
||||||
|
final double delta = Math.asin(Math.sin(lambda)
|
||||||
|
* Math.sin(Math.toRadians(23.439)));
|
||||||
|
|
||||||
|
|
||||||
|
return new SolarEquationVariables(n, m, lambda, jtransit, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the sunrise and sunset times for the given date, given
|
||||||
|
* location, and sun altitude.
|
||||||
|
* This is based on the Wikipedia article on the Sunrise equation.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate sunrise and sunset
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @param sunAltitude <a href="http://en.wikipedia.org/wiki/Solar_zenith_angle#Solar_elevation_angle">the angle between the horizon and the center of the sun's disc.</a>
|
||||||
|
* @return a two-element Gregorian Calendar array. The first element is the
|
||||||
|
* sunrise, the second element is the sunset. This will return null if there is no sunrise or sunset. (Ex: no sunrise in Antarctica in June)
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Sunrise_equation">Sunrise equation on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
public static Calendar[] getSunriseSunset(final Calendar day,
|
||||||
|
final double latitude, double longitude, double sunAltitude) {
|
||||||
|
|
||||||
|
final SolarEquationVariables solarEquationVariables = getSolarEquationVariables(day, longitude);
|
||||||
|
|
||||||
|
longitude = -longitude;
|
||||||
|
final double latitudeRad = Math.toRadians(latitude);
|
||||||
|
|
||||||
|
// Hour angle
|
||||||
|
final double omega = Math.acos((Math.sin(Math.toRadians(sunAltitude)) - Math
|
||||||
|
.sin(latitudeRad) * Math.sin(solarEquationVariables.delta))
|
||||||
|
/ (Math.cos(latitudeRad) * Math.cos(solarEquationVariables.delta)));
|
||||||
|
|
||||||
|
if (Double.isNaN(omega)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sunset
|
||||||
|
final double jset = JULIAN_DATE_2000_01_01
|
||||||
|
+ CONST_0009
|
||||||
|
+ ((Math.toDegrees(omega) + longitude) / CONST_360 + solarEquationVariables.n + 0.0053
|
||||||
|
* Math.sin(solarEquationVariables.m) - 0.0069 * Math.sin(2 * solarEquationVariables.lambda));
|
||||||
|
|
||||||
|
// Sunrise
|
||||||
|
final double jrise = solarEquationVariables.jtransit - (jset - solarEquationVariables.jtransit);
|
||||||
|
// Convert sunset and sunrise to Gregorian dates, in UTC
|
||||||
|
final Calendar gregRiseUTC = getGregorianDate(jrise);
|
||||||
|
final Calendar gregSetUTC = getGregorianDate(jset);
|
||||||
|
|
||||||
|
// Convert the sunset and sunrise to the timezone of the day parameter
|
||||||
|
final Calendar gregRise = Calendar.getInstance(day.getTimeZone());
|
||||||
|
gregRise.setTimeInMillis(gregRiseUTC.getTimeInMillis());
|
||||||
|
final Calendar gregSet = Calendar.getInstance(day.getTimeZone());
|
||||||
|
gregSet.setTimeInMillis(gregSetUTC.getTimeInMillis());
|
||||||
|
return new Calendar[]{gregRise, gregSet};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the solar noon time for the given date and given location.
|
||||||
|
* This is based on the Wikipedia article on the Sunrise equation.
|
||||||
|
*
|
||||||
|
* @param day The day for which to calculate sunrise and sunset
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return a Calendar with the time set to solar noon for the given day.
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Sunrise_equation">Sunrise equation on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
public static Calendar getSolarNoon(final Calendar day, final double latitude, double longitude) {
|
||||||
|
SolarEquationVariables solarEquationVariables = getSolarEquationVariables(day, longitude);
|
||||||
|
|
||||||
|
// Add a check for Antarctica in June and December (sun always down or up, respectively).
|
||||||
|
// In this case, jtransit will be filled in, but we need to check the hour angle omega for
|
||||||
|
// sunrise.
|
||||||
|
// If there's no sunrise (omega is NaN), there's no solar noon.
|
||||||
|
final double latitudeRad = Math.toRadians(latitude);
|
||||||
|
|
||||||
|
// Hour angle
|
||||||
|
final double omega = Math.acos((Math.sin(Math.toRadians(SUN_ALTITUDE_SUNRISE_SUNSET)) - Math
|
||||||
|
.sin(latitudeRad) * Math.sin(solarEquationVariables.delta))
|
||||||
|
/ (Math.cos(latitudeRad) * Math.cos(solarEquationVariables.delta)));
|
||||||
|
|
||||||
|
if (Double.isNaN(omega)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert jtransit Gregorian dates, in UTC
|
||||||
|
final Calendar gregNoonUTC = getGregorianDate(solarEquationVariables.jtransit);
|
||||||
|
final Calendar gregNoon = Calendar.getInstance(day.getTimeZone());
|
||||||
|
gregNoon.setTimeInMillis(gregNoonUTC.getTimeInMillis());
|
||||||
|
return gregNoon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is currently day at the given location. This returns
|
||||||
|
* true if the current time at the location is after the sunrise and
|
||||||
|
* before the sunset for that location.
|
||||||
|
*/
|
||||||
|
public static boolean isDay(double latitude, double longitude) {
|
||||||
|
Calendar now = Calendar.getInstance();
|
||||||
|
return isDay(now, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calendar a datetime
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is day at the given location and given datetime. This returns
|
||||||
|
* true if the given datetime at the location is after the sunrise and
|
||||||
|
* before the sunset for that location.
|
||||||
|
*/
|
||||||
|
public static boolean isDay(Calendar calendar, double latitude, double longitude) {
|
||||||
|
Calendar[] sunriseSunset = getSunriseSunset(calendar, latitude, longitude);
|
||||||
|
// In extreme latitudes, there may be no sunrise/sunset time in summer or
|
||||||
|
// winter, because it will be day or night 24 hours
|
||||||
|
if (sunriseSunset == null) {
|
||||||
|
int month = calendar.get(Calendar.MONTH); // Reminder: January = 0
|
||||||
|
if (latitude > 0) {
|
||||||
|
// Always night at the north pole in December
|
||||||
|
return month >= 3 && month <= 10; // Always day at the north pole in June
|
||||||
|
} else {
|
||||||
|
// Always day at the south pole in December
|
||||||
|
return month < 3 || month > 10; // Always night at the south pole in June
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Calendar sunrise = sunriseSunset[0];
|
||||||
|
Calendar sunset = sunriseSunset[1];
|
||||||
|
return calendar.after(sunrise) && calendar.before(sunset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is night at the given location currently. This returns
|
||||||
|
* true if the current time at the location is after the astronomical twilight dusk and
|
||||||
|
* before the astronomical twilight dawn for that location.
|
||||||
|
*/
|
||||||
|
public static boolean isNight(double latitude, double longitude) {
|
||||||
|
Calendar now = Calendar.getInstance();
|
||||||
|
return isNight(now, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calendar a datetime
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is night at the given location and datetime. This returns
|
||||||
|
* true if the given datetime at the location is after the astronomical twilight dusk and before
|
||||||
|
* the astronomical twilight dawn.
|
||||||
|
*/
|
||||||
|
public static boolean isNight(Calendar calendar, double latitude, double longitude) {
|
||||||
|
Calendar[] astronomicalTwilight = getAstronomicalTwilight(calendar, latitude, longitude);
|
||||||
|
if (astronomicalTwilight == null) {
|
||||||
|
int month = calendar.get(Calendar.MONTH); // Reminder: January = 0
|
||||||
|
if (latitude > 0) {
|
||||||
|
// Always night at the north pole in December
|
||||||
|
return month < 3 || month > 10; // Always day at the north pole in June
|
||||||
|
} else {
|
||||||
|
// Always day at the south pole in December
|
||||||
|
return month >= 3 && month <= 10; // Always night at the south pole in June
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Calendar dawn = astronomicalTwilight[0];
|
||||||
|
Calendar dusk = astronomicalTwilight[1];
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss z");
|
||||||
|
format.setTimeZone(calendar.getTimeZone());
|
||||||
|
return calendar.before(dawn) || calendar.after(dusk);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is currently civil twilight at the current time at the given location.
|
||||||
|
* This returns true if the current time at the location is between sunset and civil twilight dusk
|
||||||
|
* or between civil twilight dawn and sunrise.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static boolean isCivilTwilight(double latitude, double longitude) {
|
||||||
|
Calendar today = Calendar.getInstance();
|
||||||
|
return isCivilTwilight(today, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calendar the datetime for which to determine if it's civil twilight in the given location
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is civil twilight at the given location and the given calendar.
|
||||||
|
* This returns true if the given time at the location is between sunset and civil twilight dusk
|
||||||
|
* or between civil twilight dawn and sunrise.
|
||||||
|
*/
|
||||||
|
public static boolean isCivilTwilight(Calendar calendar, double latitude, double longitude) {
|
||||||
|
Calendar[] sunriseSunset = getSunriseSunset(calendar, latitude, longitude);
|
||||||
|
if (sunriseSunset == null) return false;
|
||||||
|
Calendar[] civilTwilight = getCivilTwilight(calendar, latitude, longitude);
|
||||||
|
if (civilTwilight == null) return false;
|
||||||
|
|
||||||
|
return (calendar.after(sunriseSunset[1]) && calendar.before(civilTwilight[1])
|
||||||
|
|| (calendar.after(civilTwilight[0]) && calendar.before(sunriseSunset[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is currently nautical twilight at the current time at the given location.
|
||||||
|
* This returns true if the current time at the location is between civil and nautical twilight dusk
|
||||||
|
* or between nautical and civil twilight dawn.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static boolean isNauticalTwilight(double latitude, double longitude) {
|
||||||
|
Calendar today = Calendar.getInstance();
|
||||||
|
return isNauticalTwilight(today, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calendar the datetime for which to determine if it's nautical twilight in the given location
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is nautical twilight at the given location and the given calendar.
|
||||||
|
* This returns true if the given time at the location is between civil and nautical twilight dusk
|
||||||
|
* or between nautical and civil twilight dawn.
|
||||||
|
*/
|
||||||
|
public static boolean isNauticalTwilight(Calendar calendar, double latitude, double longitude) {
|
||||||
|
Calendar[] civilTwilight = getCivilTwilight(calendar, latitude, longitude);
|
||||||
|
if (civilTwilight == null) return false;
|
||||||
|
Calendar[] nauticalTwilight = getNauticalTwilight(calendar, latitude, longitude);
|
||||||
|
if (nauticalTwilight == null) return false;
|
||||||
|
|
||||||
|
return (calendar.after(civilTwilight[1]) && calendar.before(nauticalTwilight[1])
|
||||||
|
|| (calendar.after(nauticalTwilight[0]) && calendar.before(civilTwilight[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is currently astronomical twilight at the current time at the given location.
|
||||||
|
* This returns true if the current time at the location is between nautical and astronomical twilight dusk
|
||||||
|
* or between astronomical and nautical twilight dawn.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static boolean isAstronomicalTwilight(double latitude, double longitude) {
|
||||||
|
Calendar today = Calendar.getInstance();
|
||||||
|
return isAstronomicalTwilight(today, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calendar the datetime for which to determine if it's astronomical twilight in the given location
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is astronomical twilight at the given location and the given calendar.
|
||||||
|
* This returns true if the given time at the location is between nautical and astronomical twilight dusk
|
||||||
|
* or between astronomical and nautical twilight dawn.
|
||||||
|
*/
|
||||||
|
public static boolean isAstronomicalTwilight(Calendar calendar, double latitude, double longitude) {
|
||||||
|
Calendar[] nauticalTwilight = getNauticalTwilight(calendar, latitude, longitude);
|
||||||
|
if (nauticalTwilight == null) return false;
|
||||||
|
Calendar[] astronomicalTwilight = getAstronomicalTwilight(calendar, latitude, longitude);
|
||||||
|
if (astronomicalTwilight == null) return false;
|
||||||
|
|
||||||
|
return (calendar.after(nauticalTwilight[1]) && calendar.before(astronomicalTwilight[1])
|
||||||
|
|| (calendar.after(astronomicalTwilight[0]) && calendar.before(nauticalTwilight[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return true if it is civil, nautical, or astronomical twilight currently at the given location.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static boolean isTwilight(double latitude, double longitude) {
|
||||||
|
Calendar today = Calendar.getInstance();
|
||||||
|
return isTwilight(today, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @param calendar the given datetime to check for twilight
|
||||||
|
* @return true if at the given location and calendar, it is civil, nautical, or astronomical twilight.
|
||||||
|
*/
|
||||||
|
public static boolean isTwilight(Calendar calendar, double latitude, double longitude) {
|
||||||
|
return isCivilTwilight(calendar, latitude, longitude)
|
||||||
|
|| isNauticalTwilight(calendar, latitude, longitude)
|
||||||
|
|| isAstronomicalTwilight(calendar, latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DayPeriod getDayPeriod(Calendar calendar, double latitude, double longitude) {
|
||||||
|
if (isDay(calendar, latitude, longitude)) return DayPeriod.DAY;
|
||||||
|
if (isCivilTwilight(calendar, latitude, longitude)) return DayPeriod.CIVIL_TWILIGHT;
|
||||||
|
if (isNauticalTwilight(calendar, latitude, longitude)) return DayPeriod.NAUTICAL_TWILIGHT;
|
||||||
|
if (isAstronomicalTwilight(calendar, latitude, longitude)) return DayPeriod.ASTRONOMICAL_TWILIGHT;
|
||||||
|
if (isNight(calendar, latitude, longitude)) return DayPeriod.NIGHT;
|
||||||
|
return DayPeriod.NIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calendar the datetime for which to determine the day length
|
||||||
|
* @param latitude the latitude of the location in degrees.
|
||||||
|
* @param longitude the longitude of the location in degrees (West is negative)
|
||||||
|
* @return the number of milliseconds between sunrise and sunset.
|
||||||
|
*/
|
||||||
|
public static long getDayLength(Calendar calendar, double latitude, double longitude) {
|
||||||
|
Calendar[] sunriseSunset = getSunriseSunset(calendar, latitude, longitude);
|
||||||
|
if (sunriseSunset == null) {
|
||||||
|
int month = calendar.get(Calendar.MONTH); // Reminder: January = 0
|
||||||
|
if (latitude > 0) {
|
||||||
|
if (month >= 3 && month <= 10) {
|
||||||
|
return MILLISECONDS_IN_DAY; // Always day at the north pole in June
|
||||||
|
} else {
|
||||||
|
return 0; // Always night at the north pole in December
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (month >= 3 && month <= 10) {
|
||||||
|
return 0; // Always night at the south pole in June
|
||||||
|
} else {
|
||||||
|
return MILLISECONDS_IN_DAY; // Always day at the south pole in December
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sunriseSunset[1].getTimeInMillis() - sunriseSunset[0].getTimeInMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DayPeriod {
|
||||||
|
DAY,
|
||||||
|
CIVIL_TWILIGHT,
|
||||||
|
NAUTICAL_TWILIGHT,
|
||||||
|
ASTRONOMICAL_TWILIGHT,
|
||||||
|
NIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intermediate variables used in the sunrise equation
|
||||||
|
*
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Sunrise_equation">Sunrise equation on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
private static class SolarEquationVariables {
|
||||||
|
final double n;// Julian cycle (number of days since 2000-01-01).
|
||||||
|
final double m; // solar mean anomaly
|
||||||
|
final double lambda; // ecliptic longitude
|
||||||
|
final double jtransit; // Solar transit (hour angle for solar noon)
|
||||||
|
final double delta; // Declination of the sun
|
||||||
|
|
||||||
|
private SolarEquationVariables(double n, double m, double lambda, double jtransit, double delta) {
|
||||||
|
this.n = n;
|
||||||
|
this.m = m;
|
||||||
|
this.lambda = lambda;
|
||||||
|
this.jtransit = jtransit;
|
||||||
|
this.delta = delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
8
src/main/resources/application.yml
Normal file
8
src/main/resources/application.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
shelf:
|
||||||
|
'1': #rack
|
||||||
|
'1': 26 #light/pin
|
||||||
|
'2': 20
|
||||||
|
'3': 21
|
||||||
|
latitude: 51.751187549834086
|
||||||
|
longitude: -0.33916135825242166
|
||||||
|
cronExpression: 0 5 1 * * *
|
||||||
Reference in New Issue
Block a user