diff --git a/Makefile b/Makefile index 3b5d5777..c57acba5 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ JESSIE_GO_TAGS := gtk_3_14 # Build information #GIT_COMMIT = $(shell git rev-parse HEAD | cut -c1-7) -VERSION := 2.6.0 +VERSION := 2.6.1 BUILD_DATE ?= $(shell date --utc +%Y%m%d-%H:%M:%S) #BRANCH = $(shell git rev-parse --abbrev-ref HEAD) diff --git a/README.md b/README.md index b2afb471..3b0c3bab 100755 --- a/README.md +++ b/README.md @@ -83,15 +83,15 @@ There are two ways to install OctoScreen: the recommended and supported way is t For example, to install on a new RaspberryPi with OctoPi: ```sh -wget https://github.com/Z-Bolt/OctoScreen/releases/download/2.6.0/octoscreen_2.6.0_armhf.deb -sudo dpkg -i octoscreen_2.6-0_armhf.deb +wget https://github.com/Z-Bolt/OctoScreen/releases/download/2.6.1/octoscreen_2.6.1_armhf.deb +sudo dpkg -i octoscreen_2.6.1_armhf.deb ``` Or to update an existing version of OctoScreen: ```sh -wget https://github.com/Z-Bolt/OctoScreen/releases/download/2.6.0/octoscreen_2.6.0_armhf.deb +wget https://github.com/Z-Bolt/OctoScreen/releases/download/2.6.1/octoscreen_2.6.1_armhf.deb sudo dpkg -r octoscreen -sudo dpkg -i octoscreen_2.6.0_armhf.deb +sudo dpkg -i octoscreen_2.6.1_armhf.deb sudo reboot now ``` @@ -138,14 +138,14 @@ The basic configuration is handled via environment variables, if you are using t - `OCTOPRINT_HOST` - The URL to the OctoPrint server. Example: `http://octopi.local` or `http://localhost:5000`. Note: the protocol (`http://` or `https://`) is required. If the setting for `OCTOPRINT_HOST` does not contain the protocol, an error will be displayed when OctoScreen starts. -- `OCTOPRINT_APIKEY` - OctoScreen expects an [API key]( http://docs.octoprint.org/en/master/api/general.html) to be supplied. This API key can be either the globally configured key, or a user-specific one if “Access Control” is enabled. +- `OCTOPRINT_APIKEY` - OctoScreen expects an [API key]( http://docs.octoprint.org/en/master/api/general.html) to be supplied. The API key can either be set in OctoScreen's config file, or in OctoPrint's config file (OCTOPRINT_CONFIG_FILE below) - `OCTOSCREEN_STYLE_PATH` - Several themes are supported, and style configurations can be done through CSS. This variable defines the location of the application theme. #### Optional Configuration Settings -- `OCTOPRINT_CONFIG_FILE` - The location of the OctoPrint's config.yaml file. If empty, the file path used will be the `pi` home folder of the current user. +- `OCTOPRINT_CONFIG_FILE` - The location of OctoPrint's config.yaml file. If empty, the file path used will be the `pi` home folder of the current user. The OCTOPRINT_APIKEY is required, and if it isn't defined in OctoScreen's config file (see OCTOPRINT_APIKEY above) it needs to be defined in OctoPrint's config file. - `OCTOSCREEN_LOG_FILE_PATH` - The file path to where the log file will be saved. The file path should be a fully qualified path and not only include the path to the log file, but the name of the log file as well (eg `/home/pi/logs/logfile.txt`). The log file is appended to and is never automatically truncated, and will grow over time. If you turn log file logging on (by specifying a path), be sure to turn it off (by setting the value to ""). diff --git a/debian/local/octoscreen/config b/debian/local/octoscreen/config index 6c6c88c1..002dd806 100755 --- a/debian/local/octoscreen/config +++ b/debian/local/octoscreen/config @@ -1,32 +1,49 @@ # Required Configuration Settings -# Location of the OctoPrint's config.yaml file. If empty the file will -# be search at the `pi` home folder or the current user. Only used for locally -# installed OctoPrint servers. -OCTOPRINT_CONFIG_FILE=/home/pi/.octoprint/config.yaml - -# OctoPrint HTTP address, example `http://localhost:5000`, if OctoPrint is -# locally installed will be read from the config file. +# The URL to the OctoPrint server. +# Example: http://octopi.local or http://localhost:5000. +# Note: the protocol (http:// or https://) is required. +# If the setting for OCTOPRINT_HOST does not contain the protocol, +# an error will be displayed when OctoScreen starts. OCTOPRINT_HOST=http://localhost:5000 -# OctoScreen expects an API key to be supplied. This API key can be either -# the globally configured one or a user specific one if “Access Control”. -# http://docs.octoprint.org/en/master/api/general.html, if OctoPrint is -# locally installed will be read from the config file. + +# OctoScreen expects an API key to be supplied. The API key can +# either be set in OctoScreen's config file, or in OctoPrint's config +# file (OCTOPRINT_CONFIG_FILE below) OCTOPRINT_APIKEY= +# Location of the application theme +OCTOSCREEN_STYLE_PATH=/opt/octoscreen/styles/z-bolt/ + + # Optional Configuration Settings -# Location of file for logging (optional) -OCTOSCREEN_LOG_LEVEL=Warn +# The location of OctoPrint's config.yaml file. +# If empty, the file path used will be the pi home folder of the current user. +# The OCTOPRINT_APIKEY is required, and if it isn't defined in OctoScreen's +# config file (see OCTOPRINT_APIKEY above), it needs to be defined in OctoPrint's +# config file. +OCTOPRINT_CONFIG_FILE=/home/pi/.octoprint/config.yaml -# Location of file for logging (optional) -OCTOSCREEN_LOG_FILE_PATH=logs/logfile.txt -# Location of the application theme (optional) -OCTOSCREEN_STYLE_PATH=/opt/octoscreen/styles/z-bolt/ +# The file path to where the log file will be saved. +# The file path should be a fully qualified path and not only include the path +# to the log file, but the name of the log file as well +# (eg /home/pi/logs/logfile.txt). +# The log file is appended to and is never automatically truncated, and +# will grow over time. If you turn log file logging on (by specifying a path), +# be sure to turn it off (by setting the value to ""). +OCTOSCREEN_LOG_FILE_PATH= +#OCTOSCREEN_LOG_FILE_PATH=/home/pi/logs/logfile.txt + + +# Controls the level of logging. +# Accepted values are (with increasing levels): debug, info, warn, and error. +OCTOSCREEN_LOG_LEVEL=Error + # Resolution of the application, and should be configured to the resolution of your # screen, for example 800x480. diff --git a/main.go b/main.go index 66f5e2c7..bc3d7cb4 100755 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ var ( ) func main() { - utils.Logger.Debug("entering main.main()") + utils.Logger.Debug("OctoScreen - entering main.main()") gtk.Init(nil) settings, _ := gtk.SettingsGetDefault() @@ -34,30 +34,38 @@ func main() { utils.DumpEnvironmentVariables() - if utils.RequiredEnvironmentVariablesAreSet() { + if utils.RequiredEnvironmentVariablesAreSet(APIKey) { width, height := getSize() // width and height come from EnvResolution/OCTOSCREEN_RESOLUTION // and aren't required - if not set, ui.New() will use the default // values (defined in globalVars.go). _ = ui.New(BaseURL, APIKey, width, height) } else { - fatalErrorWindow := ui.CreateFatalErrorWindow("Required environment variable is not set:", utils.NameOfMissingRequiredEnvironmentVariable()) + fatalErrorWindow := ui.CreateFatalErrorWindow("Required environment variable is not set:", utils.NameOfMissingRequiredEnvironmentVariable(APIKey)) fatalErrorWindow.ShowAll() } gtk.Main() - utils.Logger.Debug("leaving main.main()") + utils.Logger.Debug("OctoScreen - leaving main.main()") } func init() { - utils.Logger.Debug("entering main.init()") + utils.Logger.Debug("OctoScreen - entering main.init()") - if !utils.RequiredEnvironmentVariablesAreSet() { - utils.Logger.Error("main.init() - RequiredEnvironmentVariablesAreSet() returned false") + ConfigFile = os.Getenv(utils.EnvConfigFile) + if ConfigFile == "" { + ConfigFile = findConfigFile() + } + + cfg := readConfig(ConfigFile) + setApiKey(cfg) + + if !utils.RequiredEnvironmentVariablesAreSet(APIKey) { + utils.Logger.Error("OctoScreen - main.init() - RequiredEnvironmentVariablesAreSet() returned false") - utils.Logger.Debug("leaving main.init()") + utils.Logger.Debug("OctoScreen - leaving main.init()") return } @@ -65,16 +73,9 @@ func init() { utils.StylePath = os.Getenv(utils.EnvStylePath) Resolution = os.Getenv(utils.EnvResolution) - ConfigFile = os.Getenv(utils.EnvConfigFile) - if ConfigFile == "" { - ConfigFile = findConfigFile() - } - - cfg := readConfig(ConfigFile) setBaseUrl(cfg) - setApiKey(cfg) - utils.Logger.Debug("leaving main.init()") + utils.Logger.Debug("OctoScreen - leaving main.init()") } func setLogLevel() { @@ -124,13 +125,23 @@ func setBaseUrl(cfg *config) { } func setApiKey(cfg *config) { + utils.Logger.Debug("OctoScreen - entering main.setApiKey()") + APIKey = os.Getenv(utils.EnvAPIKey) if APIKey == "" { + utils.Logger.Debug("main.setApiKey() - APIKey is empty, now using cfg.API.Key") + APIKey = cfg.API.Key - if cfg.API.Key != "" { - utils.Logger.Infof("main.setApiKey() - found API key in file %q", ConfigFile) - } } + + if APIKey == "" { + utils.Logger.Debug("main.setApiKey() - APIKey is empty!") + } else { + obfuscatedApiKey := utils.GetObfuscatedValue(APIKey) + utils.Logger.Debugf("main.setApiKey() - APIKey is %q", obfuscatedApiKey) + } + + utils.Logger.Debug("OctoScreen - leaving main.setApiKey()") } diff --git a/ui/CommonPanel.go b/ui/CommonPanel.go index 6762c5ca..c030c337 100755 --- a/ui/CommonPanel.go +++ b/ui/CommonPanel.go @@ -15,7 +15,7 @@ import ( ) // OctoScreenVersion - set at compilation time. -var OctoScreenVersion = "2.6.0" +var OctoScreenVersion = "2.6.1" type CommonPanel struct { UI *UI diff --git a/ui/IdleStatusPanel.go b/ui/IdleStatusPanel.go index dd5fb549..25f85b3c 100755 --- a/ui/IdleStatusPanel.go +++ b/ui/IdleStatusPanel.go @@ -42,7 +42,7 @@ func (this *idleStatusPanel) initialize() { utils.Logger.Info(this.UI.Settings) var menuItems []octoprint.MenuItem - if this.UI.Settings == nil || len(this.UI.Settings.MenuStructure) == 0 { + if this.UI.Settings == nil || this.UI.Settings.MenuStructure == nil || len(this.UI.Settings.MenuStructure) < 1 { utils.Logger.Info("Loading default menu") this.UI.Settings.MenuStructure = getDefaultMenuItems(this.UI.Client) } else { diff --git a/ui/ui.go b/ui/ui.go index 00c67571..ed17ebee 100755 --- a/ui/ui.go +++ b/ui/ui.go @@ -178,7 +178,7 @@ func (this *UI) verifyConnection() { utils.LogError("ui.verifyConnection()", "s.Current.State is IsOffline, and (ConnectRequest)Do(UI.Client)", err) splashMessage = "Loading..." } else { - // Use 'Offline' here and 'offline' later. Having different variations may help in + // Use 'Offline.' here and 'offline!' later. Having different variations may help in // troubleshooting any issues around this state. splashMessage = "Printer is Offline." } @@ -212,14 +212,14 @@ func (this *UI) verifyConnection() { if strings.Contains(strings.ToLower(errMessage), "deadline exceeded") { // Use 'offline' here, but no ending period. - splashMessage = "Printer is offline" + splashMessage = "Printer is OFFLINE" } else { splashMessage = errMessage } } else { - // Use 'offline.' here and 'offline' above. Having different variations may help in + // Use 'offline!' here and 'OFFLINE' above. Having different variations may help in // troubleshooting any issues around this state. - splashMessage = "Printer is offline." + splashMessage = "Printer is offline!" } } @@ -264,15 +264,15 @@ func (this *UI) verifyConnection() { func (this *UI) checkNotification() { utils.Logger.Debug("entering ui.checkNotification()") + if !this.OctoPrintPluginIsAvailable { + utils.Logger.Info("ui.checkNotification() - OctoPrintPluginIsAvailable is false, so not calling GetNotification") + utils.Logger.Debug("leaving ui.checkNotification()") + return + } + notificationRespone, err := (&octoprint.GetNotificationRequest{}).Do(this.Client) if err != nil { - text := err.Error() - if strings.Contains(strings.ToLower(text), "unexpected status code: 404") { - this.OctoPrintPluginIsAvailable = false - } - utils.LogError("ui.checkNotification()", "Do(GetNotificationRequest)", err) - utils.Logger.Debug("leaving ui.checkNotification()") return } @@ -289,9 +289,22 @@ func (this *UI) loadSettings() { settingsResponse, err := (&octoprint.GetSettingsRequest{}).Do(this.Client) if err != nil { - utils.LogError("ui.loadSettings()", "Do(GetSettingsRequest)", err) - utils.Logger.Error("leaving ui.loadSettings() - Do(GetSettingsRequest) returned an error") + text := err.Error() + if strings.Contains(strings.ToLower(text), "unexpected status code: 404") { + // The call to GetSettings is also used to determine whether or not the + // OctoScreen plug-in is available. If calling GetSettings returns + // a 404, the plug-in isn't available. + this.OctoPrintPluginIsAvailable = false + utils.Logger.Info("The OctoPrint plug-in is not available") + } else { + // If we get back any other kind of error, something bad happened, so log an error. + utils.LogError("ui.loadSettings()", "Do(GetSettingsRequest)", err) + } + + utils.Logger.Debug("leaving ui.loadSettings()") return + } else { + utils.Logger.Info("The call to GetSettings succeeded and the OctoPrint plug-in is available") } if !this.validateMenuItems(settingsResponse.MenuStructure, "", true) { @@ -364,9 +377,24 @@ func (this *UI) update() { this.connectionAttempts = 0 } + if this.Settings == nil { + this.loadSettings() + + if this.Settings == nil { + this.Settings = &octoprint.GetSettingsResponse { + FilamentInLength: 750.0, + FilamentOutLength: 800.0, + ToolChanger: false, + XAxisInverted: false, + YAxisInverted: false, + ZAxisInverted: false, + MenuStructure: nil, + } + } + } + if this.OctoPrintPluginIsAvailable { this.checkNotification() - this.loadSettings() } this.verifyConnection() diff --git a/uiWidgets/SystemInformationInfoBox.go b/uiWidgets/SystemInformationInfoBox.go index 414fabb6..4f9e719f 100755 --- a/uiWidgets/SystemInformationInfoBox.go +++ b/uiWidgets/SystemInformationInfoBox.go @@ -87,7 +87,7 @@ func createStyledLabel() *gtk.Label { func (this *SystemInformationInfoBox) refreshMemoryLabel() { virtualMemoryStat, _ := mem.VirtualMemory() memoryString := fmt.Sprintf( - "Virtual memory: %s (free) / %s (total)", + "Memory: %s (free) / %s (total)", humanize.Bytes(virtualMemoryStat.Free), humanize.Bytes(virtualMemoryStat.Total), ) diff --git a/uiWidgets/TemperatureStatusBox.go b/uiWidgets/TemperatureStatusBox.go index d802d973..6cd3e18b 100755 --- a/uiWidgets/TemperatureStatusBox.go +++ b/uiWidgets/TemperatureStatusBox.go @@ -54,9 +54,11 @@ func CreateTemperatureStatusBox( hotendIndex++ if includeHotends { - strImageFileName := utils.GetNozzleFileName(hotendIndex, hotendCount) - instance.labelWithImages[key] = utils.MustLabelWithImage(strImageFileName, "") - instance.Add(instance.labelWithImages[key]) + if hotendIndex <= hotendCount { + strImageFileName := utils.GetNozzleFileName(hotendIndex, hotendCount) + instance.labelWithImages[key] = utils.MustLabelWithImage(strImageFileName, "") + instance.Add(instance.labelWithImages[key]) + } } } } diff --git a/utils/environment.go b/utils/environment.go index 6e99a382..f4f038be 100755 --- a/utils/environment.go +++ b/utils/environment.go @@ -1,6 +1,7 @@ package utils import ( + "fmt" "os" //"strings" ) @@ -20,7 +21,7 @@ const ( EnvConfigFile = "OCTOPRINT_CONFIG_FILE" ) -func RequiredEnvironmentVariablesAreSet() bool { +func RequiredEnvironmentVariablesAreSet(apiKey string) bool { if( !environmentVariableIsSet(EnvStylePath) ) { return false } @@ -29,7 +30,18 @@ func RequiredEnvironmentVariablesAreSet() bool { return false } - if( !environmentVariableIsSet(EnvAPIKey) ) { + // APIKey/OCTOPRINT_APIKEY can be set in either OctoScreen's config file, + // or in OctoPrint's config file. In main.init(), APIKey is initialized to whatever + // it can find first. + // + // APIKey is global to the "main" namespace, but the "utils" namespace is a child, + // and due to GoLang's rules, /main/utils doesn't have access to globals in /main, + // so APIKey has to be passed into RequiredEnvironmentVariablesAreSet(). + // + // if( !environmentVariableIsSet(EnvAPIKey) ) { + // return false + // } + if apiKey == "" { return false } @@ -40,7 +52,7 @@ func environmentVariableIsSet(environmentVariable string) bool { return os.Getenv(environmentVariable) != "" } -func NameOfMissingRequiredEnvironmentVariable() string { +func NameOfMissingRequiredEnvironmentVariable(apiKey string) string { if( !environmentVariableIsSet(EnvStylePath) ) { return EnvStylePath } @@ -49,7 +61,14 @@ func NameOfMissingRequiredEnvironmentVariable() string { return EnvBaseURL } - if( !environmentVariableIsSet(EnvAPIKey) ) { + // Similar comment as to the one that's in RequiredEnvironmentVariablesAreSet()... + // Since the runtime value of APIKey is set in main.init(), and can be set by either + // being defined in OctoScreen's config file or in OctoPrint's config file, + // the value needs to be passed into NameOfMissingRequiredEnvironmentVariable(). + // if( !environmentVariableIsSet(EnvAPIKey) ) { + // return EnvAPIKey + // } + if apiKey == "" { return EnvAPIKey } @@ -64,7 +83,7 @@ func DumpEnvironmentVariables() { // Required environment variables Logger.Infof("Required environment variables:") dumpEnvironmentVariable(EnvBaseURL) - dumpEnvironmentVariable(EnvAPIKey) + dumpObfuscatedEnvironmentVariable(EnvAPIKey) dumpEnvironmentVariable(EnvStylePath) // Optional environment variables @@ -89,3 +108,32 @@ func dumpEnvironmentVariable(key string) { Logger.Infof("key: %q, value: %q", key, value) } + +func dumpObfuscatedEnvironmentVariable(key string) { + value := os.Getenv(key) + if value == "" { + value = ">>MISSING<<" + } + + Logger.Infof("key: %q, value: %q", key, GetObfuscatedValue(value)) +} + +func GetObfuscatedValue(value string) string { + length := len(value) + + obfuscatedValue := "" + if length < 6 { + obfuscatedValue = "!!!INVALID!!!" + } else { + obfuscatedValue = fmt.Sprintf("%c%c%c---%c%c%c", + value[0], + value[1], + value[2], + value[length - 3], + value[length - 2], + value[length - 1], + ) + } + + return obfuscatedValue +} diff --git a/utils/tools.go b/utils/tools.go index f01e6360..dab1ae55 100755 --- a/utils/tools.go +++ b/utils/tools.go @@ -16,24 +16,25 @@ func GetToolheadCount(client *octoprint.Client) int { return cachedToolheadCount } - c, err := (&octoprint.ConnectionRequest{}).Do(client) + connectionResponse, err := (&octoprint.ConnectionRequest{}).Do(client) if err != nil { LogError("toolhaad.GetToolheadCount()", "Do(ConnectionRequest)", err) return 0 } - profile, err := (&octoprint.PrinterProfilesRequest{Id: c.Current.PrinterProfile}).Do(client) + printerProfile, err := (&octoprint.PrinterProfilesRequest{Id: connectionResponse.Current.PrinterProfile}).Do(client) if err != nil { LogError("toolhaad.GetToolheadCount()", "Do(PrinterProfilesRequest)", err) return 0 } - if profile.Extruder.SharedNozzle { + cachedToolheadCount = printerProfile.Extruder.Count + if printerProfile.Extruder.SharedNozzle { cachedToolheadCount = 1 + } else if cachedToolheadCount > 4 { + cachedToolheadCount = 4 } - cachedToolheadCount = profile.Extruder.Count - // TESTING: uncomment to force all toolheads to display and use for testing // cachedToolheadCount = 2