Skip to content

Commit

Permalink
Refactor in client module, POC for analyze subprogram working
Browse files Browse the repository at this point in the history
  • Loading branch information
retrodaredevil committed Dec 10, 2024
1 parent 4528134 commit edf6fe1
Show file tree
Hide file tree
Showing 50 changed files with 802 additions and 294 deletions.
4 changes: 4 additions & 0 deletions client/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ dependencies {
// https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket
implementation 'org.java-websocket:Java-WebSocket:1.5.5'

// TODO Remove jewelcli dependency and transition to commons-cli
// https://mvnrepository.com/artifact/com.lexicalscope.jewelcli/jewelcli
implementation group: 'com.lexicalscope.jewelcli', name: 'jewelcli', version: '0.8.9'

// https://mvnrepository.com/artifact/commons-cli/commons-cli
implementation group: 'commons-cli', name: 'commons-cli', version: '1.9.0'

implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
// https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-jul
implementation group: 'org.apache.logging.log4j', name: 'log4j-jul', version: log4jVersion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ private void handle(EventsApiEnvelope eventsApiEnvelope) {
LOGGER.debug("Got a message! type: " + eventsApiEnvelope.getType() + " payload: " + eventsApiEnvelope.getPayload());
if ("events_api".equals(eventsApiEnvelope.getType())) {
JsonObject payload = eventsApiEnvelope.getPayload().getAsJsonObject();
// https://api.slack.com/events/message
JsonObject message = payload.getAsJsonObject("event");
// TODO create a hash of this message
if (message.get("bot_id") != null) {
LOGGER.debug("Got a message from a bot! Ignoring.");
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import me.retrodaredevil.solarthing.packets.security.crypto.InvalidKeyException;
import me.retrodaredevil.solarthing.packets.security.crypto.KeyUtil;
import me.retrodaredevil.solarthing.packets.security.crypto.PublicKeyLookUp;
import me.retrodaredevil.solarthing.program.SecurityPacketReceiver;
import me.retrodaredevil.solarthing.program.subprogram.run.SecurityPacketReceiver;
import me.retrodaredevil.solarthing.reason.ExecutionReason;
import me.retrodaredevil.solarthing.reason.OpenSourceExecutionReason;
import me.retrodaredevil.solarthing.type.alter.AlterPacket;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import me.retrodaredevil.solarthing.actions.environment.EventReceiverEnvironment;
import me.retrodaredevil.solarthing.actions.environment.ExecutionReasonEnvironment;
import me.retrodaredevil.solarthing.packets.Packet;
import me.retrodaredevil.solarthing.program.PacketListReceiverHandler;
import me.retrodaredevil.solarthing.program.subprogram.run.PacketListReceiverHandler;
import me.retrodaredevil.solarthing.reason.ExecutionReason;
import me.retrodaredevil.solarthing.type.event.feedback.ImmutableExecutionFeedbackPacket;
import org.slf4j.Logger;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package me.retrodaredevil.solarthing.actions.environment;

import me.retrodaredevil.solarthing.program.PacketListReceiverHandler;
import me.retrodaredevil.solarthing.program.subprogram.run.PacketListReceiverHandler;

/**
* An environment that contains objects used to put data in the event database
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import me.retrodaredevil.solarthing.annotations.JsonExplicit;
import me.retrodaredevil.solarthing.packets.identification.IdentifierFragmentMatcher;
import me.retrodaredevil.solarthing.packets.identification.IdentifierRepFragment;
import me.retrodaredevil.solarthing.program.pvoutput.provider.PacketVoltageProvider;
import me.retrodaredevil.solarthing.program.pvoutput.provider.PacketTemperatureCelsiusProvider;
import me.retrodaredevil.solarthing.program.pvoutput.provider.TemperatureCelsiusProvider;
import me.retrodaredevil.solarthing.program.pvoutput.provider.VoltageProvider;
import me.retrodaredevil.solarthing.program.subprogram.pvoutput.provider.PacketVoltageProvider;
import me.retrodaredevil.solarthing.program.subprogram.pvoutput.provider.PacketTemperatureCelsiusProvider;
import me.retrodaredevil.solarthing.program.subprogram.pvoutput.provider.TemperatureCelsiusProvider;
import me.retrodaredevil.solarthing.program.subprogram.pvoutput.provider.VoltageProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,244 +1,19 @@
package me.retrodaredevil.solarthing.program;

import com.lexicalscope.jewel.cli.ArgumentValidationException;
import com.lexicalscope.jewel.cli.Cli;
import com.lexicalscope.jewel.cli.CliFactory;
import com.lexicalscope.jewel.cli.HelpRequestedException;
import me.retrodaredevil.couchdbjava.exception.CouchDbCodeException;
import me.retrodaredevil.couchdbjava.exception.CouchDbException;
import me.retrodaredevil.couchdbjava.response.ErrorResponse;
import me.retrodaredevil.solarthing.SolarThingConstants;
import me.retrodaredevil.solarthing.annotations.UtilityClass;
import me.retrodaredevil.solarthing.config.ConfigException;
import me.retrodaredevil.solarthing.config.ConfigUtil;
import me.retrodaredevil.solarthing.config.databases.DatabaseConfig;
import me.retrodaredevil.solarthing.config.databases.DatabaseSettings;
import me.retrodaredevil.solarthing.config.databases.implementations.CouchDbDatabaseSettings;
import me.retrodaredevil.solarthing.config.options.*;
import me.retrodaredevil.solarthing.packets.Packet;
import me.retrodaredevil.solarthing.packets.collection.HourIntervalPacketCollectionIdGenerator;
import me.retrodaredevil.solarthing.packets.collection.PacketCollectionIdGenerator;
import me.retrodaredevil.solarthing.packets.creation.TextPacketCreator;
import me.retrodaredevil.solarthing.packets.handling.PacketListReceiver;
import me.retrodaredevil.solarthing.packets.handling.RawPacketReceiver;
import me.retrodaredevil.solarthing.packets.instance.InstanceFragmentIndicatorPackets;
import me.retrodaredevil.solarthing.packets.instance.InstanceSourcePackets;
import me.retrodaredevil.solarthing.program.action.RunActionMain;
import me.retrodaredevil.solarthing.program.subprogram.analyze.AnalyzeMain;
import me.retrodaredevil.solarthing.program.check.CheckMain;
import me.retrodaredevil.solarthing.program.couchdb.CouchDbSetupMain;
import me.retrodaredevil.solarthing.program.pvoutput.PVOutputUploadMain;
import org.apache.logging.log4j.LogManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import me.retrodaredevil.solarthing.program.subprogram.run.RunMain;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.List;
import java.util.Random;

import static java.util.Objects.requireNonNull;

@UtilityClass
public final class SolarMain {
private SolarMain(){ throw new UnsupportedOperationException(); }

/*
So you think this class is complicated? Well I'll have to agree with you on that. This is probably the most
complicated class in this project. However, that's a good thing. By having most of the complexity in this class,
we have less complexity in other classes. Most configuration of how the actual program works goes on in here.
I'm sure there's a better way to write this class. However the main advantage of it right now is that
it's pretty easy to change behaviour, and that's what's important.
*/

private static final Logger LOGGER = LoggerFactory.getLogger(SolarMain.class);

public static int initReader(InputStream in, Runnable reloadIO, TextPacketCreator packetCreator, RawPacketReceiver rawPacketReceiver) {
SolarReader solarReader = new SolarReader(in, packetCreator, rawPacketReceiver);
try {
while (!Thread.currentThread().isInterrupted()) {
try {
solarReader.update();
} catch (EOFException e) {
return 0;
} catch (IOException e) {
LOGGER.error("Got IOException!", e);
Thread.sleep(500);
reloadIO.run();
LOGGER.debug("Reloaded IO bundle");
Thread.sleep(1000);
}
Thread.sleep(5);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return SolarThingConstants.EXIT_CODE_INTERRUPTED;
}

public static PacketListReceiver getSourceAndFragmentUpdater(PacketHandlingOption options){
String source = options.getSourceId();
int fragment = options.getFragmentId();
requireNonNull(source);
Packet sourcePacket = InstanceSourcePackets.create(source);
Packet fragmentPacket = InstanceFragmentIndicatorPackets.create(fragment);
return (list) -> {
list.add(sourcePacket);
list.add(fragmentPacket);
};
}
public static PacketCollectionIdGenerator createIdGenerator(Integer uniqueIdsInOneHour, boolean shortDocumentId){
if(uniqueIdsInOneHour == null){
return PacketCollectionIdGenerator.Defaults.UNIQUE_GENERATOR;
}
return new HourIntervalPacketCollectionIdGenerator(uniqueIdsInOneHour, new Random().nextInt(), shortDocumentId);
}

private static String getJarInfo() {
JarUtil.Data data = JarUtil.getData();
Instant lastModified = data.getLastModifiedInstantOrNull();
return "Jar: " + JarUtil.getJarFileName()
+ " Last Modified: " + (lastModified == null ? "unknown" : lastModified.toString())
+ " Java version: " + System.getProperty("java.version");
}

public static int doMainCommand(CommandOptions commandOptions, Path baseConfigFile) {
String user = System.getProperty("user.name");
if (!"solarthing".equals(user) && !SolarThingEnvironment.isRunningInDocker()) {
if (user.equals("root")) {
LOGGER.warn("Running as root user!");
System.out.println("\n\nHey! We noticed you are running as root! Instead of\n sudo ./run.sh\nPlease do\n sudo -u solarthing ./run.sh\n instead.\n");
} else {
LOGGER.info("Running as " + user);
}
}

LOGGER.info("Using base configuration file: " + baseConfigFile);
if (Files.notExists(baseConfigFile)) {
LOGGER.error(SolarThingConstants.SUMMARY_MARKER, "(Fatal)Base configuration file does not exist!");
return SolarThingConstants.EXIT_CODE_INVALID_CONFIG;
}
final ProgramOptions options;
try {
options = ConfigUtil.readConfig(baseConfigFile, ProgramOptions.class);
} catch (ConfigException e) {
LOGGER.error(SolarThingConstants.SUMMARY_MARKER, "(Fatal)Error while parsing ProgramOptions.", e);
LOGGER.info(SolarThingConstants.SUMMARY_MARKER, "Hey! The error you got above might be scary, but this message might be helpful:\n\n" + e.getMessage());
return SolarThingConstants.EXIT_CODE_INVALID_CONFIG;
}

// Note we used to have the creation of the .data directory here. We may consider adding it back in the future should we need it

final ProgramType programType = options.getProgramType();
try {
if(programType == ProgramType.MATE) {
return OutbackMateMain.connectMate((MateProgramOptions) options, commandOptions.isValidate());
} else if(programType == ProgramType.ROVER_SETUP){
return RoverMain.connectRoverSetup((RoverSetupProgramOptions) options, commandOptions.isValidate());
} else if(programType == ProgramType.PVOUTPUT_UPLOAD){
return PVOutputUploadMain.startPVOutputUpload((PVOutputUploadProgramOptions) options, commandOptions, commandOptions.isValidate());
} else if(programType == ProgramType.REQUEST) {
return RequestMain.startRequestProgram((RequestProgramOptions) options, commandOptions.isValidate());
} else if(programType == ProgramType.AUTOMATION) {
return AutomationMain.startAutomation((AutomationProgramOptions) options, commandOptions.isValidate());
}
throw new AssertionError("Unknown program type... type=" + programType + " programOptions=" + options);
} catch (ConfigException e) {
String logMessage = "Ending SolarThing. " + getJarInfo();
LOGGER.error(SolarThingConstants.SUMMARY_MARKER, "[LOG] " + logMessage);
System.out.println("[stdout] " + logMessage);
System.err.println("[stderr] " + logMessage);
LOGGER.error(SolarThingConstants.SUMMARY_MARKER, "(Fatal)Got config exception", e);
LOGGER.info(SolarThingConstants.SUMMARY_MARKER, "SolarThing is shutting down. This is caused by an error in your configuration or in how your environment is set up. Detailed message below:\n\n" + e.getUserMessage());
LogManager.shutdown();
return SolarThingConstants.EXIT_CODE_INVALID_CONFIG;
} catch (Throwable t) {
boolean invalidJar = t instanceof ClassNotFoundException || t instanceof NoClassDefFoundError;
if (invalidJar) {
LOGGER.info(SolarThingConstants.SUMMARY_MARKER, "We're about to give you an error with some technical stuff, but this error is likely caused by you switching out jar files while SolarThing is running. If it isn't, please report this error.");
}
boolean uncommonError = t instanceof UnsatisfiedLinkError;
if (uncommonError) {
LOGGER.info(SolarThingConstants.SUMMARY_MARKER, "Got an UnsatisfiedLinkError which is uncommon. If setup correctly, after this crash program will relaunch (hopefully successfully).");
}
String logMessage = "Ending SolarThing. " + getJarInfo();
LOGGER.error(SolarThingConstants.SUMMARY_MARKER, "[LOG] " + logMessage);
System.out.println("[stdout] " + logMessage);
System.err.println("[stderr] " + logMessage);
LOGGER.error(SolarThingConstants.SUMMARY_MARKER, "(Fatal)Got throwable", t);
LOGGER.debug("Going to shutdown LogManager.");
LogManager.shutdown(); // makes sure all buffered logs are flushed // this should be done automatically, but we'll do it anyway
System.err.println();
t.printStackTrace(System.err); // print to stderr just in case logging isn't going well
if (invalidJar) {
return SolarThingConstants.EXIT_CODE_RESTART_NEEDED_JAR_UPDATED;
}
if (uncommonError) {
return SolarThingConstants.EXIT_CODE_RESTART_NEEDED_UNCOMMON_ERROR;
}
return SolarThingConstants.EXIT_CODE_CRASH;
}
}

private static int doMain(String[] args){
String logMessage = "Beginning main. " + getJarInfo();
LOGGER.info(SolarThingConstants.SUMMARY_MARKER, "[LOG] " + logMessage);
System.out.println("[stdout] " + logMessage);
System.err.println("[stderr] " + logMessage);
Cli<CommandOptions> cli = CliFactory.createCli(CommandOptions.class);
final CommandOptions commandOptions;
try {
commandOptions = cli.parseArguments(args);
} catch (ArgumentValidationException ex) {
System.out.println(cli.getHelpMessage());
if (ex instanceof HelpRequestedException) {
return 0;
}
LOGGER.error(SolarThingConstants.SUMMARY_MARKER, ex.getMessage());
LOGGER.error(SolarThingConstants.SUMMARY_MARKER, "(Fatal)Incorrect args");
return SolarThingConstants.EXIT_CODE_INVALID_OPTIONS;
}
if (commandOptions.getBaseConfigFile() != null) {
return doMainCommand(commandOptions, Path.of(commandOptions.getBaseConfigFile()));
}
if (commandOptions.getCouchDbSetupFile() != null) {
final DatabaseConfig config;
try {
config = ConfigUtil.readConfig(Path.of(commandOptions.getCouchDbSetupFile()), DatabaseConfig.class);
} catch (ConfigException e) {
e.printStackTrace();
System.err.println("Problem reading CouchDB database settings file.");
return SolarThingConstants.EXIT_CODE_INVALID_CONFIG;
}
DatabaseSettings settings = config.requireDatabaseSettings();
if (!(settings instanceof CouchDbDatabaseSettings)) {
System.err.println("Must be CouchDB database settings!");
return SolarThingConstants.EXIT_CODE_INVALID_CONFIG;
}
try {
return CouchDbSetupMain.createFrom((CouchDbDatabaseSettings) settings).doCouchDbSetupMain();
} catch (CouchDbException e) {
if (e instanceof CouchDbCodeException) {
ErrorResponse error = ((CouchDbCodeException) e).getErrorResponse();
if (error != null) {
System.err.println(error.getError());
System.err.println(error.getReason());
}
}
throw new RuntimeException(e);
}
}
List<String> legacyArguments = commandOptions.getLegacyOptionsRaw();
if (legacyArguments == null || legacyArguments.isEmpty()) {
System.err.println(cli.getHelpMessage());
return SolarThingConstants.EXIT_CODE_INVALID_OPTIONS;
}
System.err.println("Invalid sub command: " + legacyArguments.get(0));
return SolarThingConstants.EXIT_CODE_INVALID_OPTIONS;
}
private static int outputVersion() {
JarUtil.Data data = JarUtil.getData();
Instant lastModified = data.getLastModifiedInstantOrNull();
Expand All @@ -252,28 +27,32 @@ private static int outputVersion() {
"Java version: " + System.getProperty("java.version"));
return 0;
}

private static int determineMainSubprogram(String[] args) {
if (args.length == 0) {
System.err.println("Usage: solarthing [command] [args]\n\n" +
"Commands:\n" +
" run [options]\n" +
" version\n" +
" check --port <serial port> [--type <type>]\n" +
" action [file]");
" action [file]\n" +
" analyze [options]");
return SolarThingConstants.EXIT_CODE_INVALID_OPTIONS;
}
String firstArg = args[0];
String[] subArgs = new String[args.length - 1];
System.arraycopy(args, 1, subArgs, 0, args.length - 1);
switch (firstArg) {
case "run":
return doMain(subArgs);
return RunMain.runMain(subArgs);
case "version":
return outputVersion();
case "action":
return RunActionMain.runAction(subArgs);
case "check":
return CheckMain.doCheck(subArgs);
case "analyze":
return AnalyzeMain.analyze(subArgs);
default:
System.err.println("Unknown argument: " + firstArg + "\n" +
"Previous SolarThing versions allowed the running of SolarThing without the 'run' prefix.\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import me.retrodaredevil.solarthing.annotations.UtilityClass;
import me.retrodaredevil.solarthing.packets.handling.PacketListReceiver;
import me.retrodaredevil.solarthing.packets.handling.implementations.TimedPacketReceiver;
import me.retrodaredevil.solarthing.program.SolarReader;
import me.retrodaredevil.solarthing.program.subprogram.run.SolarReader;
import me.retrodaredevil.solarthing.program.modbus.MutableAddressModbusSlave;
import me.retrodaredevil.solarthing.solar.common.BatteryVoltage;
import me.retrodaredevil.solarthing.solar.outback.MatePacketCreator49;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import me.retrodaredevil.solarthing.commands.packets.open.RequestHeartbeatPacket;
import me.retrodaredevil.solarthing.packets.Packet;
import me.retrodaredevil.solarthing.packets.collection.TargetPacketGroup;
import me.retrodaredevil.solarthing.program.PacketListReceiverHandler;
import me.retrodaredevil.solarthing.program.subprogram.run.PacketListReceiverHandler;
import me.retrodaredevil.solarthing.reason.ExecutionReason;
import me.retrodaredevil.solarthing.reason.OpenSourceExecutionReason;
import me.retrodaredevil.solarthing.type.event.feedback.HeartbeatData;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package me.retrodaredevil.solarthing.program.subprogram.analyze;

import static java.util.Objects.requireNonNull;

public class AnalysisException extends Exception {
public AnalysisException(String message, Throwable cause) {
super(requireNonNull(message), requireNonNull(cause));
}
}
Loading

0 comments on commit edf6fe1

Please sign in to comment.