use of me.retrodaredevil.action.node.environment.ActionEnvironment in project solarthing by wildmountainfarms.
the class CommandController method runCommand.
/**
* Runs the given action. A response is not returned until the action is done running
*
* @param apiKey The api key
* @param commandName The name of the command, which corresponds to an action
* @return
*/
@GetMapping(path = "/run", produces = "application/json")
public CommandRequestResponse runCommand(String apiKey, String commandName) {
// Also consider using this way instead of exceptions: https://stackoverflow.com/a/60079942
if (apiKey == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "apiKey is required!");
}
if (commandName == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "commandName is required!");
}
if (!commandHandler.isAuthorized(apiKey)) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "You are not authorized with the given api key!");
}
ActionNode actionNode = commandHandler.getActionNode(commandName);
if (actionNode == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No corresponding command with commandName: " + commandName);
}
InjectEnvironment injectEnvironment = commandHandler.createInjectEnvironment(commandName);
// We don't know or care what thread this is running on, so we won't have a shared global variable environment.
// We could make a shared global environment a feature of this down the line, but for now let's keep this simple
ActionEnvironment actionEnvironment = new ActionEnvironment(new VariableEnvironment(), new VariableEnvironment(), injectEnvironment);
Action action = actionNode.createAction(actionEnvironment);
while (!action.isDone()) {
action.update();
try {
// This is here to make sure our CPU doesn't go to 100% for no reason
Thread.sleep(5);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOGGER.error("Interrupted while action was being performed.", e);
action.end();
return new CommandRequestResponse(false);
}
}
action.end();
return new CommandRequestResponse(true);
}
use of me.retrodaredevil.action.node.environment.ActionEnvironment in project solarthing by wildmountainfarms.
the class ActionNodeDataReceiver method receiveData.
private void receiveData(OpenSource source, String commandName) {
ActionNode requested = actionNodeMap.get(commandName);
if (requested != null) {
InjectEnvironment.Builder injectEnvironmentBuilder = new InjectEnvironment.Builder();
environmentUpdater.updateInjectEnvironment(source, injectEnvironmentBuilder);
Action action = requested.createAction(new ActionEnvironment(variableEnvironment, new VariableEnvironment(), injectEnvironmentBuilder.build()));
// Now that action has been created, add it to the action multiplexer. (Adding is thread safe).
// The action has not been used by this thread, so when a different thread starts executing it, there will be no problems.
actionMultiplexer.add(action);
LOGGER.info(SolarThingConstants.SUMMARY_MARKER, source.getSender() + " has requested command sequence: " + commandName);
} else {
LOGGER.info(SolarThingConstants.SUMMARY_MARKER, "Sender: " + source.getSender() + " has requested unknown command: " + commandName);
}
}
use of me.retrodaredevil.action.node.environment.ActionEnvironment in project solarthing by wildmountainfarms.
the class AutomationMain method startAutomation.
public static int startAutomation(List<ActionNode> actionNodes, DatabaseTimeZoneOptionBase options, long periodMillis) {
LOGGER.info(SolarThingConstants.SUMMARY_MARKER, "Starting automation program.");
final CouchDbDatabaseSettings couchSettings;
try {
couchSettings = ConfigUtil.expectCouchDbDatabaseSettings(options);
} catch (IllegalArgumentException ex) {
LOGGER.error("(Fatal)", ex);
return SolarThingConstants.EXIT_CODE_INVALID_CONFIG;
}
SolarThingDatabase database = CouchDbSolarThingDatabase.create(CouchDbUtil.createInstance(couchSettings.getCouchProperties(), couchSettings.getOkHttpProperties()));
VariableEnvironment variableEnvironment = new VariableEnvironment();
// Use atomic reference so that access is thread safe
AtomicReference<FragmentedPacketGroup> latestPacketGroupReference = new AtomicReference<>(null);
// Use atomic reference so that access is thread safe
AtomicReference<List<VersionedPacket<StoredAlterPacket>>> alterPacketsReference = new AtomicReference<>(null);
// note this may return null, and that's OK // This is thread safe if needed
FragmentedPacketGroupProvider fragmentedPacketGroupProvider = latestPacketGroupReference::get;
Clock clock = Clock.systemUTC();
SimpleDatabaseCache statusDatabaseCache = SimpleDatabaseCache.createDefault(clock);
// not thread safe
ResourceManager<SimpleDatabaseCache> statusDatabaseCacheManager = new BasicResourceManager<>(statusDatabaseCache);
SimpleDatabaseCache eventDatabaseCache = SimpleDatabaseCache.createDefault(clock);
ResourceManager<SimpleDatabaseCache> eventDatabaseCacheManager = new ReadWriteResourceManager<>(eventDatabaseCache);
SimpleDatabaseCache openDatabaseCache = new SimpleDatabaseCache(Duration.ofMinutes(60), Duration.ofMinutes(40), Duration.ofMinutes(20), Duration.ofMinutes(15), clock);
// not thread safe
ResourceManager<SimpleDatabaseCache> openDatabaseCacheManager = new BasicResourceManager<>(openDatabaseCache);
SimplePacketCache<AuthorizationPacket> authorizationPacketCache = new SimplePacketCache<>(Duration.ofSeconds(20), DatabaseDocumentKeyMap.createPacketSourceFromDatabase(database), false);
String sourceId = options.getSourceId();
InjectEnvironment injectEnvironment = new InjectEnvironment.Builder().add(new NanoTimeProviderEnvironment(NanoTimeProvider.SYSTEM_NANO_TIME)).add(new SourceIdEnvironment(sourceId)).add(// most of the time, it's better to use SolarThingDatabaseEnvironment instead, but this option is here in case it's needed
new CouchDbEnvironment(couchSettings)).add(new SolarThingDatabaseEnvironment(CouchDbSolarThingDatabase.create(CouchDbUtil.createInstance(couchSettings.getCouchProperties(), couchSettings.getOkHttpProperties())))).add(new TimeZoneEnvironment(options.getZoneId())).add(// access is thread safe if needed
new LatestPacketGroupEnvironment(fragmentedPacketGroupProvider)).add(// access is thread safe if needed
new LatestFragmentedPacketGroupEnvironment(fragmentedPacketGroupProvider)).add(new EventDatabaseCacheEnvironment(eventDatabaseCacheManager)).add(new OpenDatabaseCacheEnvironment(openDatabaseCache)).add(// access is thread safe if needed
new AlterPacketsEnvironment(alterPacketsReference::get)).add(new AuthorizationEnvironment(new DatabaseDocumentKeyMap(authorizationPacketCache))).build();
ActionMultiplexer multiplexer = new Actions.ActionMultiplexerBuilder().build();
while (!Thread.currentThread().isInterrupted()) {
queryAndFeed(database.getStatusDatabase(), statusDatabaseCacheManager, true);
queryAndFeed(database.getEventDatabase(), eventDatabaseCacheManager, true);
queryAndFeed(database.getOpenDatabase(), openDatabaseCacheManager, false);
{
// Never cache alter packets, because it's always important that we have up-to-date data, or no data at all.
List<VersionedPacket<StoredAlterPacket>> alterPackets = null;
try {
alterPackets = database.getAlterDatabase().queryAll(sourceId);
LOGGER.debug("Got " + alterPackets.size() + " alter packets");
} catch (SolarThingDatabaseException e) {
LOGGER.error("Could not get alter packets", e);
}
alterPacketsReference.set(alterPackets);
}
// we have auto update turned off, so we have to call this
authorizationPacketCache.updateIfNeeded();
List<FragmentedPacketGroup> statusPacketGroups = PacketUtil.getPacketGroups(options.getSourceId(), options.getDefaultInstanceOptions(), statusDatabaseCache.getAllCachedPackets());
if (statusPacketGroups != null) {
FragmentedPacketGroup statusPacketGroup = statusPacketGroups.get(statusPacketGroups.size() - 1);
latestPacketGroupReference.set(statusPacketGroup);
}
for (ActionNode actionNode : actionNodes) {
multiplexer.add(actionNode.createAction(new ActionEnvironment(variableEnvironment, new VariableEnvironment(), injectEnvironment)));
}
multiplexer.update();
LOGGER.debug("There are " + multiplexer.getActiveActions().size() + " active actions");
try {
Thread.sleep(periodMillis);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new RuntimeException(ex);
}
}
return 0;
}
use of me.retrodaredevil.action.node.environment.ActionEnvironment in project solarthing by wildmountainfarms.
the class SlackChatBotActionNode method createAction.
@Override
public Action createAction(ActionEnvironment actionEnvironment) {
LatestFragmentedPacketGroupEnvironment latestPacketGroupEnvironment = actionEnvironment.getInjectEnvironment().get(LatestFragmentedPacketGroupEnvironment.class);
AlterPacketsEnvironment alterPacketsEnvironment = actionEnvironment.getInjectEnvironment().get(AlterPacketsEnvironment.class);
SolarThingDatabaseEnvironment solarThingDatabaseEnvironment = actionEnvironment.getInjectEnvironment().get(SolarThingDatabaseEnvironment.class);
SourceIdEnvironment sourceIdEnvironment = actionEnvironment.getInjectEnvironment().get(SourceIdEnvironment.class);
TimeZoneEnvironment timeZoneEnvironment = actionEnvironment.getInjectEnvironment().get(TimeZoneEnvironment.class);
EventDatabaseCacheEnvironment eventDatabaseCacheEnvironment = actionEnvironment.getInjectEnvironment().get(EventDatabaseCacheEnvironment.class);
// Note that all objects listed here must be thread safe, as data will be accessed from them on a separate thread
FragmentedPacketGroupProvider packetGroupProvider = latestPacketGroupEnvironment.getFragmentedPacketGroupProvider();
AlterPacketsProvider alterPacketsProvider = alterPacketsEnvironment.getAlterPacketsProvider();
SolarThingDatabase database = solarThingDatabaseEnvironment.getSolarThingDatabase();
String sourceId = sourceIdEnvironment.getSourceId();
ZoneId zoneId = timeZoneEnvironment.getZoneId();
ResourceManager<? extends DatabaseCache> eventDatabaseCacheManager = eventDatabaseCacheEnvironment.getEventDatabaseCacheManager();
Slack slack = Slack.getInstance(new SlackConfig(), new SlackHttpClient(new OkHttpClient.Builder().callTimeout(Duration.ofSeconds(10)).connectTimeout(Duration.ofSeconds(4)).build()));
ChatBotCommandHelper commandHelper = new ChatBotCommandHelper(permissionMap, packetGroupProvider, new CommandManager(keyDirectory, sender));
return new SlackChatBotAction(appToken, new SlackMessageSender(authToken, channelId, slack), slack, new HelpChatBotHandler(new ChatBotHandlerMultiplexer(Arrays.asList(// note: this isn't applied to "help" commands
new StaleMessageHandler(), new ScheduleCommandChatBotHandler(commandHelper, database, sourceId, zoneId), new CancelCommandChatBotHandler(commandHelper, database, sourceId, zoneId, alterPacketsProvider), new FlagCommandChatBotHandler(commandHelper, database, sourceId, zoneId, alterPacketsProvider), new CommandChatBotHandler(commandHelper, database, sourceId, zoneId), new StatusChatBotHandler(packetGroupProvider, alterPacketsProvider), new HeartbeatCommandChatBotHandler(eventDatabaseCacheManager), (message, messageSender) -> {
messageSender.sendMessage("Unknown command!");
return true;
}))));
}
use of me.retrodaredevil.action.node.environment.ActionEnvironment in project solarthing by wildmountainfarms.
the class DeserializeTest method testRunAlertGeneratorOffWhileAuxOn.
@Test
void testRunAlertGeneratorOffWhileAuxOn() throws IOException, ParsePacketAsciiDecimalDigitException, CheckSumException {
File file = new File(ACTION_CONFIG_DIRECTORY, "alert_generator_off_while_aux_on.json");
ActionNode actionNode = MAPPER.readValue(file, ActionNode.class);
// We need to simulate an automation program environment to run this action
Duration[] timeReference = new Duration[] { Duration.ZERO };
FragmentedPacketGroup[] packetGroupReference = new FragmentedPacketGroup[] { null };
FragmentedPacketGroupProvider fragmentedPacketGroupProvider = () -> requireNonNull(packetGroupReference[0]);
InjectEnvironment injectEnvironment = new InjectEnvironment.Builder().add(new NanoTimeProviderEnvironment(() -> timeReference[0].toNanos())).add(new LatestPacketGroupEnvironment(fragmentedPacketGroupProvider)).add(new LatestFragmentedPacketGroupEnvironment(fragmentedPacketGroupProvider)).build();
FXStatusPacket auxOnNoAC = FXStatusPackets.createFromChars("\n1,00,00,02,123,123,00,10,000,00,252,136,000,999\r".toCharArray(), IgnoreCheckSum.IGNORE);
FXStatusPacket auxOffNoAC = FXStatusPackets.createFromChars("\n1,00,00,02,123,123,00,10,000,00,252,008,000,999\r".toCharArray(), IgnoreCheckSum.IGNORE);
FXStatusPacket auxOnACUse = FXStatusPackets.createFromChars("\n1,00,00,02,123,123,00,10,000,02,252,136,000,999\r".toCharArray(), IgnoreCheckSum.IGNORE);
FXStatusPacket auxOffACUse = FXStatusPackets.createFromChars("\n1,00,00,02,123,123,00,10,000,02,252,008,000,999\r".toCharArray(), IgnoreCheckSum.IGNORE);
for (FXStatusPacket packet : new FXStatusPacket[] { auxOffNoAC, auxOnACUse, auxOffACUse }) {
// for these three cases, the action should end immediately
packetGroupReference[0] = PacketGroups.createInstancePacketGroup(Collections.singleton(packet), 0L, "my_source_id", 999);
Action action = actionNode.createAction(new ActionEnvironment(new VariableEnvironment(), new VariableEnvironment(), injectEnvironment));
action.update();
assertTrue(action.isDone());
}
{
// Test that no alert is sent unless the aux is on, and it's in No AC for 30 seconds
packetGroupReference[0] = PacketGroups.createInstancePacketGroup(Collections.singleton(auxOnNoAC), 0L, "my_source_id", 999);
Action action = actionNode.createAction(new ActionEnvironment(new VariableEnvironment(), new VariableEnvironment(), injectEnvironment));
action.update();
assertFalse(action.isDone());
timeReference[0] = timeReference[0].plus(Duration.ofSeconds(29));
action.update();
assertFalse(action.isDone());
packetGroupReference[0] = PacketGroups.createInstancePacketGroup(Collections.singleton(auxOnACUse), 0L, "my_source_id", 999);
action.update();
// No alert has been sent, since it started to AC Use before the 30 second period completed.
assertTrue(action.isDone());
}
{
// Test that the alert gets sent and the action doesn't end until the 300-second timeout completes
packetGroupReference[0] = PacketGroups.createInstancePacketGroup(Collections.singleton(auxOnNoAC), 0L, "my_source_id", 999);
Action action = actionNode.createAction(new ActionEnvironment(new VariableEnvironment(), new VariableEnvironment(), injectEnvironment));
action.update();
assertFalse(action.isDone());
timeReference[0] = timeReference[0].plus(Duration.ofSeconds(30));
action.update();
assertFalse(action.isDone());
packetGroupReference[0] = PacketGroups.createInstancePacketGroup(Collections.singleton(auxOnACUse), 0L, "my_source_id", 999);
action.update();
// Alert has been sent, so the action isn't going to end
assertFalse(action.isDone());
timeReference[0] = timeReference[0].plus(Duration.ofSeconds(299));
action.update();
assertFalse(action.isDone());
timeReference[0] = timeReference[0].plus(Duration.ofSeconds(1));
action.update();
// the 300-second timeout has completed, so the action will end
assertTrue(action.isDone());
}
}
Aggregations