Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable auto-reconnect and supporting changes. #252

Open
wants to merge 3 commits into
base: 2.7.1-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ The basic configuration is handled via environment variables, if you are using t

- `OCTOSCREEN_RESOLUTION` - Resolution of the application, and should be configured to the resolution of your screen. Optimal resolution for OctoScreen is no less than 800x480, so if the physical resolution of your screen is 480x320, it's recommended to set the software resolution 800x533. If you are using Raspbian you can do it by changing [`hdmi_cvt`](https://www.raspberrypi.org/documentation/configuration/config-txt/video.md) param in `/boot/config.txt` file. Please see [Setting Up OctoScreen and Your Display](https://github.com/Z-Bolt/OctoScreen/wiki/Setting-Up-OctoScreen-and-Your-Display) and [Installing OctoScreen with a 3.5" 480x320 TFT screen](https://github.com/Z-Bolt/OctoScreen/wiki/Installing-OctoScreen-with-a-3.5%22-480x320-TFT-screen) for more information.

- `OCTOSCREEN_AUTO_RECONNECT` - Controls wether OctoScreen automatically triggers a Connect attempt when it detects the Printer is not connected. Defaults to `TRUE`. Dissabling this feature releases control of Printer connection to OctoPrint, and may provide better compatibility when flashing or interfacing with the printer controller via an external device.




Expand Down
10 changes: 10 additions & 0 deletions debian/local/octoscreen/config
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ OCTOSCREEN_LOG_FILE_PATH=
OCTOSCREEN_LOG_LEVEL=Error


# Controls automatic reconnect behaviour
# Wether or not OctoScreen should instruct OctoPrint to reconnect to the Printer
# automatically.
# Default setting is TRUE, which will, in effect, prevent you from "Disconecting"
# from your printer via the web interface, as OctoScreen will immediatly attempt
# to reconnect. Set this to FALSE to allow OctoPrint to manage the connection
# internally.
#OCTOSCREEN_AUTO_RECONNECT=FALSE


# Resolution of the application, and should be configured to the resolution of your
# screen, for example 800x480.
OCTOSCREEN_RESOLUTION=800x480
Expand Down
39 changes: 25 additions & 14 deletions ui/SplashPanel.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ type SplashPanel struct {
}

func NewSplashPanel(ui *UI) *SplashPanel {
instane := &SplashPanel {
instance := &SplashPanel {
CommonPanel: NewCommonPanel(ui, nil),
}
instane.initialize()
instance.initialize()

return instane
return instance
}

func (this *SplashPanel) initialize() {
Expand Down Expand Up @@ -58,7 +58,7 @@ func (this *SplashPanel) createActionBar() gtk.IWidget {
actionBar := utils.MustBox(gtk.ORIENTATION_HORIZONTAL, 5)
actionBar.SetHAlign(gtk.ALIGN_END)

this.RetryButton = utils.MustButtonImageStyle("Retry", "refresh.svg", "color2", this.releaseFromHold)
this.RetryButton = utils.MustButtonImageStyle("Reconnect", "refresh.svg", "color2", this.doReconnect)
this.RetryButton.SetProperty("width-request", this.Scaled(100))
this.RetryButton.SetProperty("visible", true)
actionBar.Add(this.RetryButton)
Expand All @@ -77,32 +77,43 @@ func (this *SplashPanel) createActionBar() gtk.IWidget {
return actionBar
}

func (this *SplashPanel) putOnHold() {
logger.TraceEnter("SplashPanel.putOnHold()")
func (this *SplashPanel) displayReconnect() {
logger.TraceEnter("SplashPanel.displayReconnect()")

this.RetryButton.Show()
ctx, err := this.RetryButton.GetStyleContext()
if err != nil {
logger.LogError("SplashPanel.putOnHold()", "RetryButton.GetStyleContext()", err)
logger.LogError("SplashPanel.displayReconnect()", "RetryButton.GetStyleContext()", err)
} else {
ctx.RemoveClass("hidden")
}
this.Label.SetText("Cannot connect to the printer. Tap \"Retry\" to try again.")
this.Label.SetText("Cannot connect to the printer. Tap \"Reconnect\" to try again.")

logger.TraceLeave("SplashPanel.putOnHold()")
logger.TraceLeave("SplashPanel.displayReconnect()")
}

func (this *SplashPanel) releaseFromHold() {
logger.TraceEnter("SplashPanel.releaseFromHold()")

func (this *SplashPanel) hideReconnect() {
logger.TraceEnter("SplashPanel.hideReconnect()")
this.RetryButton.Hide()
ctx, _ := this.RetryButton.GetStyleContext()
ctx.AddClass("hidden")
logger.TraceEnter("SplashPanel.hideReconnect()")
}

func (this *SplashPanel) doReconnect() {
logger.TraceEnter("SplashPanel.doReconnect()")

this.hideReconnect()

this.Label.SetText("Loading...")
this.UI.connectionAttempts = 0

if this.UI.DoReconnect() {
this.Label.SetText("Attempting to reconnect...")
} else {
this.Label.SetText("ERROR: Unable to reconnect...")
}

logger.TraceLeave("SplashPanel.releaseFromHold()")
logger.TraceLeave("SplashPanel.doReconnect()")
}

func (this *SplashPanel) showNetwork() {
Expand Down
68 changes: 50 additions & 18 deletions ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package ui
import (
"fmt"
"strings"
"strconv"
"sync"
"time"
"os"

"github.com/coreos/go-systemd/daemon"
"github.com/golang-collections/collections/stack"
Expand All @@ -19,6 +21,15 @@ import (
"github.com/Z-Bolt/OctoScreen/utils"
)

const (
connectionAttemptsMax = 8
connectionAttemptsDisplayRetry = 3
updateTaskTimer = time.Second * 10
)

var (
errMercyPeriod = time.Second * 10
)

type UI struct {
sync.Mutex
Expand All @@ -44,10 +55,19 @@ type UI struct {
height int
scaleFactor int
connectionAttempts int

autoReconnect bool
}

func New(endpoint, key string, width, height int) *UI {
logger.TraceEnter("ui.New()")

autoReconnect := utils.AutoReconnect

if utils.EnvironmentVariableIsSet(utils.EnvAutoReconnect) {
autoReconnectBool, _ := strconv.ParseBool(os.Getenv(utils.EnvAutoReconnect))
autoReconnect = autoReconnectBool
}

if width == 0 || height == 0 {
width = utils.WindowWidth
Expand All @@ -68,6 +88,8 @@ func New(endpoint, key string, width, height int) *UI {

width: width,
height: height,

autoReconnect: autoReconnect,
}

instance.window.Connect("configure-event", func(win *gtk.Window) {
Expand Down Expand Up @@ -100,7 +122,7 @@ func New(endpoint, key string, width, height int) *UI {
}

instance.splashPanel = NewSplashPanel(instance)
instance.backgroundTask = utils.CreateBackgroundTask(time.Second * 10, instance.update)
instance.backgroundTask = utils.CreateBackgroundTask(updateTaskTimer, instance.update)
instance.initialize()

logger.TraceLeave("ui.New()")
Expand Down Expand Up @@ -151,8 +173,6 @@ func (this *UI) loadStyle() {
logger.TraceLeave("ui.loadStyle()")
}

var errMercyPeriod = time.Second * 10

func (this *UI) verifyConnection() {
logger.TraceEnter("ui.verifyConnection()")

Expand Down Expand Up @@ -213,13 +233,7 @@ func (this *UI) getUiStateAndMessageFromConnectionResponse(
case connectionResponse.Current.State.IsOffline():
logger.Debug("ui.getUiStateAndMessageFromConnectionResponse() - the state is now offline and displaying the splash panel")
newUIState = "splash"
logger.Info("ui.getUiStateAndMessageFromConnectionResponse() - new UI state is 'splash' and is about to call ConnectRequest.Do()")
if err := (&octoprintApis.ConnectRequest{}).Do(this.Client); err != nil {
logger.LogError("ui.getUiStateAndMessageFromConnectionResponse()", "s.Current.State is IsOffline, and (ConnectRequest)Do(UI.Client)", err)
splashMessage = "Loading..."
} else {
splashMessage = "Printer is offline, now trying to connect..."
}
splashMessage = "Printer is offline, waiting for OctoPrint to connect..."

case connectionResponse.Current.State.IsConnecting():
logger.Debug("ui.getUiStateAndMessageFromConnectionResponse() - new state is splash (from IsConnecting)")
Expand Down Expand Up @@ -259,16 +273,16 @@ func (this *UI) getUiStateAndMessageFromError(
logger.Infof("ui.getUiStateAndMessageFromError() - errMessage is: %q", errMessage)

if strings.Contains(strings.ToLower(errMessage), "deadline exceeded") {
splashMessage = "Printer is offline (deadline exceeded), retrying to connect..."
splashMessage = "Printer is offline (deadline exceeded), attempting to reconnect..."
} else if strings.Contains(strings.ToLower(errMessage), "connection reset by peer") {
splashMessage = "Printer is offline (peer connection reset), retrying to connect..."
splashMessage = "Printer is offline (peer connection reset), attempting to reconnect..."
} else if strings.Contains(strings.ToLower(errMessage), "unexpected status code: 403") {
splashMessage = "Printer is offline (403), retrying to connect..."
splashMessage = "Printer is offline (403), attempting to reconnect..."
} else {
splashMessage = errMessage
}
} else {
splashMessage = "Printer is offline! (retrying to connect...)"
splashMessage = "Printer is offline! (attempting to reconnect...)"
}

logger.TraceLeave("ui.getUiStateAndMessageFromError()")
Expand Down Expand Up @@ -421,15 +435,23 @@ func (this *UI) validateMenuItems(menuItems []dataModels.MenuItem, name string,
return true
}

func (this *UI) DoReconnect() bool {
logger.Info("UI.DoReconnect() - about to call ConnectRequest.Do()")
if err := (&octoprintApis.ConnectRequest{}).Do(this.Client); err != nil {
logger.LogError("UI.DoReconnect()", "(ConnectRequest)Do(UI.Client)", err)
return false
} else {
return true
}
}

func (this *UI) update() {
logger.TraceEnter("ui.update()")

this.sdNotify(daemon.SdNotifyWatchdog)

if this.connectionAttempts > 8 {
logger.Info("ui.update() - this.connectionAttempts > 8")
this.splashPanel.putOnHold()

if this.connectionAttempts > connectionAttemptsMax {
logger.Info("ui.update() - this.connectionAttempts > %d", connectionAttemptsMax)
logger.TraceLeave("ui.update()")
return
}
Expand Down Expand Up @@ -463,6 +485,16 @@ func (this *UI) update() {
}

this.verifyConnection()

if this.UIState == "splash" {
if this.connectionAttempts == connectionAttemptsDisplayRetry {
logger.Info("ui.update() - this.connectionAttempts == %d", connectionAttemptsDisplayRetry)
this.splashPanel.displayReconnect()
}
if this.ConnectionState.IsOffline() && this.autoReconnect {
this.DoReconnect()
}
}

logger.TraceLeave("ui.update()")
}
Expand Down
16 changes: 9 additions & 7 deletions utils/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ const (
EnvLogFilePath = "OCTOSCREEN_LOG_FILE_PATH"
EnvResolution = "OCTOSCREEN_RESOLUTION"
EnvConfigFile = "OCTOPRINT_CONFIG_FILE"
EnvAutoReconnect = "OCTOPRINT_AUTO_RECONNECT"
)

func RequiredEnvironmentVariablesAreSet(apiKey string) bool {
if( !environmentVariableIsSet(EnvStylePath) ) {
if( !EnvironmentVariableIsSet(EnvStylePath) ) {
return false
}

if( !environmentVariableIsSet(EnvBaseURL) ) {
if( !EnvironmentVariableIsSet(EnvBaseURL) ) {
return false
}

Expand All @@ -45,7 +46,7 @@ func RequiredEnvironmentVariablesAreSet(apiKey string) bool {
// 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) ) {
// if( !EnvironmentVariableIsSet(EnvAPIKey) ) {
// return false
// }
if apiKey == "" {
Expand All @@ -55,24 +56,24 @@ func RequiredEnvironmentVariablesAreSet(apiKey string) bool {
return true
}

func environmentVariableIsSet(environmentVariable string) bool {
func EnvironmentVariableIsSet(environmentVariable string) bool {
return os.Getenv(environmentVariable) != ""
}

func NameOfMissingRequiredEnvironmentVariable(apiKey string) string {
if( !environmentVariableIsSet(EnvStylePath) ) {
if( !EnvironmentVariableIsSet(EnvStylePath) ) {
return EnvStylePath
}

if( !environmentVariableIsSet(EnvBaseURL) ) {
if( !EnvironmentVariableIsSet(EnvBaseURL) ) {
return EnvBaseURL
}

// 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) ) {
// if( !EnvironmentVariableIsSet(EnvAPIKey) ) {
// return EnvAPIKey
// }
if apiKey == "" {
Expand Down Expand Up @@ -110,6 +111,7 @@ func DumpEnvironmentVariables() {
dumpEnvironmentVariable(EnvLogFilePath)
dumpEnvironmentVariable(EnvLogLevel)
dumpEnvironmentVariable(EnvResolution)
dumpEnvironmentVariable(EnvAutoReconnect)
// EnvResolution is optional. If not set, the window size will
// default to the values defined in globalVars.go.
}
Expand Down
1 change: 1 addition & 0 deletions utils/globalVars.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var (
WindowName = "OctoScreen"
WindowWidth = 800
WindowHeight = 480
AutoReconnect = true
)

const (
Expand Down