From 35bbcd7a6d75be940e35088c0fc29b0da02abd38 Mon Sep 17 00:00:00 2001 From: Thomas Klaehn Date: Wed, 10 Sep 2025 08:39:58 +0200 Subject: [PATCH] Initial commit Signed-off-by: Thomas Klaehn --- .vscode/launch.json | 16 +++ Makefile | 61 ++++++++++ config/octoprint/docker-compose.yml | 40 +++++++ printctrl.service | 10 ++ src/app/process/octoprint/octoprint.go | 37 ++++++ src/app/process/process.go | 77 ++++++++++++ src/go.mod | 9 ++ src/go.sum | 8 ++ src/internal/apiservice/data/data.go | 98 +++++++++++++++ src/internal/apiservice/power/power.go | 98 +++++++++++++++ src/internal/apiservice/printer/printer.go | 132 +++++++++++++++++++++ src/internal/apiservice/relay/relay.go | 130 ++++++++++++++++++++ src/main.go | 40 +++++++ webui/css/slider.css | 61 ++++++++++ webui/css/style.css | 14 +++ webui/index.html | 113 ++++++++++++++++++ 16 files changed, 944 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 Makefile create mode 100644 config/octoprint/docker-compose.yml create mode 100644 printctrl.service create mode 100644 src/app/process/octoprint/octoprint.go create mode 100644 src/app/process/process.go create mode 100644 src/go.mod create mode 100644 src/go.sum create mode 100644 src/internal/apiservice/data/data.go create mode 100644 src/internal/apiservice/power/power.go create mode 100644 src/internal/apiservice/printer/printer.go create mode 100644 src/internal/apiservice/relay/relay.go create mode 100644 src/main.go create mode 100644 webui/css/slider.css create mode 100644 webui/css/style.css create mode 100644 webui/index.html diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c8882a5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/src/main.go" + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7f6f6fc --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +PROJECT_NAME := printctrl + +PREFIX ?= /usr/bin + +CONFIG_DIR := /etc/$(PROJECT_NAME) +SYSTEM_DIR := /usr/lib/systemd/system +WEB_DIR := /var/lib/$(PROJECT_NAME) + +CONFIG_FILE := config/octoprint/docker-compose.yml +BIN_FILE := build/bin/$(PROJECT_NAME) +UNIT_FILE := $(PROJECT_NAME).service +README_FILE := README.md + +.PHONY: all +all: service + +.PHONY: service +service: + mkdir -p bin + cd src && go build -o ../$(BIN_FILE) + +.PHONY: clean +clean: + go clean + rm -rf bin + -rm $(PROJECT_NAME).tar.gz + +.PHONY: install +install: all + echo $(PWD) + @if [ -f $(CONFIG_DIR)/octoprint/$(notdir $(CONFIG_FILE)) ]; then \ + echo "$(CONFIG_DIR)/$(notdir $(CONFIG_FILE)) already exists - skipping..."; \ + else \ + install -d $(CONFIG_DIR)/octoprint; \ + install -m 0644 $(CONFIG_FILE) $(CONFIG_DIR)/octoprint; \ + echo "install -d $(CONFIG_DIR)/octoprint"; \ + echo "install -m 0644 $(CONFIG_FILE) $(CONFIG_DIR)/octoprint"; \ + fi + + # Binary + install -d $(PREFIX) + install -m 0755 $(BIN_FILE) $(PREFIX) + + # System unit + install -d $(SYSTEM_DIR) + install -m 0644 $(UNIT_FILE) $(SYSTEM_DIR) + + # Web ui + install -d $(WEB_DIR) + cp -r webui/* $(WEB_DIR) + +.PHONY: uninstall +uninstall: + rm -rf $(CONFIG_DIR) + rm -rf $(SYSTEM_DIR)/$(UNIT_FILE) + rm -rf $(PREFIX)/$(PROJECT_NAME) + rm -rf $(WEB_DIR) + +.PHONY: package +package: all + tar cvzf $(PROJECT_NAME).tar.gz $(CONFIG_FILE) $(BIN_FILE) $(UNIT_FILE) $(README_FILE) webui/build/* diff --git a/config/octoprint/docker-compose.yml b/config/octoprint/docker-compose.yml new file mode 100644 index 0000000..7c149a5 --- /dev/null +++ b/config/octoprint/docker-compose.yml @@ -0,0 +1,40 @@ +version: '2.4' + +services: + octoprint: + image: octoprint/octoprint + restart: unless-stopped + ports: + - 80:80 + devices: + # use `python -m serial.tools.miniterm` to see what the name is of the printer, this requires pyserial + - /dev/ttyUSB0:/dev/ttyUSB0 + - /dev/video0:/dev/video0 + volumes: + - octoprint:/octoprint + # uncomment the lines below to ensure camera streaming is enabled when + # you add a video device + environment: + - ENABLE_MJPG_STREAMER=true + + #### + # uncomment if you wish to edit the configuration files of octoprint + # refer to docs on configuration editing for more information + #### + + #config-editor: + # image: linuxserver/code-server + # ports: + # - 8443:8443 + # depends_on: + # - octoprint + # restart: unless-stopped + # environment: + # - PUID=0 + # - PGID=0 + # - TZ=America/Chicago + # volumes: + # - octoprint:/octoprint + +volumes: + octoprint: diff --git a/printctrl.service b/printctrl.service new file mode 100644 index 0000000..7e7904a --- /dev/null +++ b/printctrl.service @@ -0,0 +1,10 @@ +[Unit] +Description=3d printer control service +After=multi-user.target + +[Service] +Type=idle +ExecStart=/usr/bin/printctrl -w /var/lib/printctrl + +[Install] +WantedBy=multi-user.target diff --git a/src/app/process/octoprint/octoprint.go b/src/app/process/octoprint/octoprint.go new file mode 100644 index 0000000..135a758 --- /dev/null +++ b/src/app/process/octoprint/octoprint.go @@ -0,0 +1,37 @@ +package octoprint + +import ( + "log" + "powerswitch/app/process" +) + +const ( + start_cmd = "docker-compose -f /etc/printctrl/octoprint/docker-compose.yml up -d" + stop_cmd = "docker-compose -f /etc/printctrl/octoprint/docker-compose.yml down" +) + +var ( + logger log.Logger = *log.Default() +) + +func init() { + logger.SetFlags(log.Llongfile | log.Ltime) +} + +func Start() error { + p := process.NewProcess(start_cmd) + return p.Start() +} + +func Stop() error { + p := process.NewProcess(stop_cmd) + return p.Start() +} + +func ReStart() error { + err := Stop() + if err != nil { + logger.Print(err) + } + return Start() +} diff --git a/src/app/process/process.go b/src/app/process/process.go new file mode 100644 index 0000000..a8ac3cd --- /dev/null +++ b/src/app/process/process.go @@ -0,0 +1,77 @@ +package process + +import ( + "bufio" + "io" + "log" + "os/exec" +) + +var ( + logger log.Logger = *log.Default() +) + +func init() { + logger.SetFlags(log.Llongfile | log.Ltime) +} + +type Process struct { + StdoutChannel chan string + StderrChannel chan string + + process *exec.Cmd + stdout io.ReadCloser + stderr io.ReadCloser +} + +func NewProcess(command string) *Process { + p := new(Process) + p.process = exec.Command("bash", "-c", command) + var err error + p.stdout, err = p.process.StdoutPipe() + if err != nil { + logger.Panic(err) + } + p.stderr, err = p.process.StderrPipe() + if err != nil { + logger.Panic(err) + } + p.StdoutChannel = make(chan string) + p.StderrChannel = make(chan string) + return p +} + +func (p Process) Start() error { + err := p.process.Start() + if err != nil { + return err + } + return nil +} + +func (p Process) Observe() { + go func() { + scanner := bufio.NewScanner(p.stdout) + for scanner.Scan() { + p.StdoutChannel <- scanner.Text() + } + }() + go func() { + scanner := bufio.NewScanner(p.stderr) + for scanner.Scan() { + p.StderrChannel <- scanner.Text() + } + }() +} + +func (p Process) Kill() { + p.process.Process.Kill() +} + +func (p Process) Wait() error { + err := p.process.Wait() + if err != nil { + return err + } + return nil +} diff --git a/src/go.mod b/src/go.mod new file mode 100644 index 0000000..f408a2a --- /dev/null +++ b/src/go.mod @@ -0,0 +1,9 @@ +module powerswitch + +go 1.22.6 + +require ( + github.com/taigrr/systemctl v1.0.10 + periph.io/x/conn/v3 v3.7.2 + periph.io/x/host/v3 v3.8.5 +) diff --git a/src/go.sum b/src/go.sum new file mode 100644 index 0000000..b2e8a17 --- /dev/null +++ b/src/go.sum @@ -0,0 +1,8 @@ +github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/taigrr/systemctl v1.0.10 h1:J1ifqf9wXYpbGYjCOgDIz9niFLCdlpNpIHRn9cA1J7g= +github.com/taigrr/systemctl v1.0.10/go.mod h1:TpeHkNuHgYT63FI5jVLBf5VNAGbxEFH3FHqg5ReXnd0= +periph.io/x/conn/v3 v3.7.2 h1:qt9dE6XGP5ljbFnCKRJ9OOCoiOyBGlw7JZgoi72zZ1s= +periph.io/x/conn/v3 v3.7.2/go.mod h1:Ao0b4sFRo4QOx6c1tROJU1fLJN1hUIYggjOrkIVnpGg= +periph.io/x/host/v3 v3.8.5 h1:g4g5xE1XZtDiGl1UAJaUur1aT7uNiFLMkyMEiZ7IHII= +periph.io/x/host/v3 v3.8.5/go.mod h1:hPq8dISZIc+UNfWoRj+bPH3XEBQqJPdFdx218W92mdc= diff --git a/src/internal/apiservice/data/data.go b/src/internal/apiservice/data/data.go new file mode 100644 index 0000000..ce61a1d --- /dev/null +++ b/src/internal/apiservice/data/data.go @@ -0,0 +1,98 @@ +package apiservice_data + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + apiservice_relay "powerswitch/internal/apiservice/relay" +) + +type state string + +type State struct { + State state `json:"state"` +} + +const ( + StateOn state = "on" + StateOff state = "off" + id int = 1 +) + +var ( + logger log.Logger = *log.Default() +) + +func init() { + logger.SetFlags(log.Llongfile | log.Ltime) +} + +func AddHandler() { + http.HandleFunc("GET /data/state", handle_get_powerstate) + http.HandleFunc("PATCH /data/state", handle_patch_powerstate) +} + +func GetState() (bool, error) { + res, err := apiservice_relay.GetRelay(id) + return !res, err +} + +func SetState(state bool) error { + return apiservice_relay.SetRelay(id, !state) +} + +func handle_get_powerstate(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-type", "application/json; charset=utf-8;") + state, err := apiservice_relay.GetRelay(id) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + var res State + if state { + res.State = StateOff + } else { + res.State = StateOn + } + tmp, err := json.Marshal(res) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + w.WriteHeader(http.StatusOK) + w.Write(tmp) +} + +func handle_patch_powerstate(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-type", "application/json; charset=utf-8;") + + tmp, err := io.ReadAll(r.Body) + if err != nil { + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + w.WriteHeader(http.StatusInternalServerError) + return + } + var state State + err = json.Unmarshal(tmp, &state) + if err != nil { + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + w.WriteHeader(http.StatusInternalServerError) + return + } + + set_state := true + if state.State == StateOn { + set_state = false + } + err = apiservice_relay.SetRelay(id, set_state) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + w.WriteHeader(http.StatusOK) +} diff --git a/src/internal/apiservice/power/power.go b/src/internal/apiservice/power/power.go new file mode 100644 index 0000000..e513872 --- /dev/null +++ b/src/internal/apiservice/power/power.go @@ -0,0 +1,98 @@ +package apiservice_power + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + apiservice_relay "powerswitch/internal/apiservice/relay" +) + +type state string + +type State struct { + State state `json:"state"` +} + +const ( + StateOn state = "on" + StateOff state = "off" + id int = 0 +) + +var ( + logger log.Logger = *log.Default() +) + +func init() { + logger.SetFlags(log.Llongfile | log.Ltime) +} + +func AddHandler() { + http.HandleFunc("GET /power/state", handle_get_powerstate) + http.HandleFunc("PATCH /power/state", handle_patch_powerstate) +} + +func GetState() (bool, error) { + res, err := apiservice_relay.GetRelay(id) + return !res, err +} + +func SetState(state bool) error { + return apiservice_relay.SetRelay(id, !state) +} + +func handle_get_powerstate(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-type", "application/json; charset=utf-8;") + state, err := apiservice_relay.GetRelay(id) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + var res State + if state { + res.State = StateOff + } else { + res.State = StateOn + } + tmp, err := json.Marshal(res) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + w.WriteHeader(http.StatusOK) + w.Write(tmp) +} + +func handle_patch_powerstate(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-type", "application/json; charset=utf-8;") + + tmp, err := io.ReadAll(r.Body) + if err != nil { + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + w.WriteHeader(http.StatusInternalServerError) + return + } + var state State + err = json.Unmarshal(tmp, &state) + if err != nil { + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + w.WriteHeader(http.StatusInternalServerError) + return + } + + set_state := true + if state.State == StateOn { + set_state = false + } + err = apiservice_relay.SetRelay(id, set_state) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + w.WriteHeader(http.StatusOK) +} diff --git a/src/internal/apiservice/printer/printer.go b/src/internal/apiservice/printer/printer.go new file mode 100644 index 0000000..7337f68 --- /dev/null +++ b/src/internal/apiservice/printer/printer.go @@ -0,0 +1,132 @@ +package apiservice_printer + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "powerswitch/app/process/octoprint" + apiservice_data "powerswitch/internal/apiservice/data" + apiservice_power "powerswitch/internal/apiservice/power" +) + +type state string + +type State struct { + State state `json:"state"` +} + +const ( + StateOn state = "on" + StateOff state = "off" +) + +var ( + logger log.Logger = *log.Default() +) + +func init() { + logger.SetFlags(log.Llongfile | log.Ltime) +} + +func AddHandler() { + http.HandleFunc("GET /printer/state", handle_get_printerstate) + http.HandleFunc("PATCH /printer/state", handle_patch_printerstate) +} + +func handle_get_printerstate(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-type", "application/json; charset=utf-8;") + data_state, err := apiservice_data.GetState() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + + power_state, err := apiservice_power.GetState() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + var res State + if data_state && power_state { + res.State = StateOn + } else { + res.State = StateOff + } + tmp, err := json.Marshal(res) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + w.WriteHeader(http.StatusOK) + w.Write(tmp) +} + +func handle_patch_printerstate(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-type", "application/json; charset=utf-8;") + + tmp, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + var state State + err = json.Unmarshal(tmp, &state) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + + if state.State == StateOn { + err := apiservice_data.SetState(true) + if err != nil { + logger.Print(err) + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + err = apiservice_power.SetState(true) + if err != nil { + logger.Print(err) + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + err = octoprint.ReStart() + if err != nil { + logger.Print(err) + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + } else { + err := octoprint.Stop() + if err != nil { + logger.Print(err) + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + err = apiservice_power.SetState(false) + if err != nil { + logger.Print(err) + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + err = apiservice_data.SetState(false) + if err != nil { + logger.Print(err) + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + return + } + } + w.WriteHeader(http.StatusOK) +} diff --git a/src/internal/apiservice/relay/relay.go b/src/internal/apiservice/relay/relay.go new file mode 100644 index 0000000..fffc183 --- /dev/null +++ b/src/internal/apiservice/relay/relay.go @@ -0,0 +1,130 @@ +package apiservice_relay + +import ( + "encoding/json" + "errors" + "io" + "log" + "net/http" + "strconv" + + "periph.io/x/conn/v3/gpio" + "periph.io/x/conn/v3/gpio/gpioreg" +) + +type relay struct { + Id int `json:"id"` + Value bool `json:"value"` +} + +var ( + logger log.Logger = *log.Default() + gpios = []string{"GPIO26", "GPIO20", "GPIO21"} +) + +func init() { + logger.SetFlags(log.Llongfile | log.Ltime) +} + +func GetRelay(id int) (bool, error) { + if id >= len(gpios) { + return false, errors.New("index out of range") + } + p := gpioreg.ByName(gpios[id]) + if p == nil { + return false, errors.New("gpio not found") + } + state := p.Read() + return bool(state), nil +} + +func SetRelay(id int, state bool) error { + if id >= len(gpios) { + return errors.New("index out of range") + } + p := gpioreg.ByName(gpios[id]) + if p == nil { + return errors.New("gpio not found") + } + return p.Out(gpio.Level(state)) +} + +func AddHandler() { + http.HandleFunc("GET /relay/{id}", handle_get_relays) + http.HandleFunc("PATCH /relay/{id}", handle_patch_relays) +} + +func handle_get_relays(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-type", "application/json; charset=utf-8;") + id, err := strconv.Atoi(r.PathValue("id")) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + if id >= len(gpios) { + logger.Print("Index out of range: ", id) + w.WriteHeader(http.StatusNotImplemented) + return + } + p := gpioreg.ByName(gpios[id]) + if p == nil { + logger.Print("unable to find gpio") + w.WriteHeader(http.StatusNotImplemented) + return + } + state := p.Read() + logger.Print("state: ", state) + var rel relay + rel.Id = id + rel.Value = bool(state) + res, err := json.Marshal(rel) + if err != nil { + logger.Print("unable marshal obj to json") + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Write(res) + w.WriteHeader(http.StatusOK) +} + +func handle_patch_relays(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-type", "application/json; charset=utf-8;") + + id, err := strconv.Atoi(r.PathValue("id")) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + if id >= len(gpios) { + logger.Print("Index out of range: ", id) + w.WriteHeader(http.StatusNotImplemented) + return + } + + tmp, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(`{"error": "cannot read reqest body"}`)) + return + } + var rel relay + err = json.Unmarshal(tmp, &rel) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(`{"error": "cannot unmarshal json to object"}`)) + return + } + p := gpioreg.ByName(gpios[id]) + if p == nil { + logger.Print("unable to find gpio") + w.WriteHeader(http.StatusNotImplemented) + return + } + err = p.Out(gpio.Level(rel.Value)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write(json.RawMessage(`{"error": "cannot set gpio"}`)) + return + } + w.WriteHeader(http.StatusOK) +} diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..f7e1bee --- /dev/null +++ b/src/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "flag" + "log" + "net/http" + + apiservice_data "powerswitch/internal/apiservice/data" + apiservice_power "powerswitch/internal/apiservice/power" + apiservice_printer "powerswitch/internal/apiservice/printer" + apiservice_relay "powerswitch/internal/apiservice/relay" + + host "periph.io/x/host/v3" +) + +var ( + logger log.Logger = *log.Default() +) + +func init() { + logger.SetFlags(log.Llongfile | log.Ltime) + logger.Println("Starting") +} + +func main() { + var webui_path string + flag.StringVar(&webui_path, "w", "../webui", "Specify path to serve the web ui. Default is ../webui") + flag.Parse() + + host.Init() + + apiservice_data.AddHandler() + apiservice_power.AddHandler() + apiservice_printer.AddHandler() + apiservice_relay.AddHandler() + + port := ":5005" + http.Handle("/", http.FileServer(http.Dir(webui_path))) + logger.Fatal(http.ListenAndServe(port, nil)) +} diff --git a/webui/css/slider.css b/webui/css/slider.css new file mode 100644 index 0000000..a4cc803 --- /dev/null +++ b/webui/css/slider.css @@ -0,0 +1,61 @@ +.switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; +} + +/* Hide default HTML checkbox */ +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +/* The slider */ +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider { + background-color: #5b5b5b; +} + +input:focus + .slider { + box-shadow: 0 0 1px #2196F3; +} + +input:checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 34px; +} + +.slider.round:before { + border-radius: 50%; +} diff --git a/webui/css/style.css b/webui/css/style.css new file mode 100644 index 0000000..0123e51 --- /dev/null +++ b/webui/css/style.css @@ -0,0 +1,14 @@ +html, body { + height: 100%; +} + +html { + display: table; + margin: auto; + font-family: Arial, Helvetica, sans-serif; +} + +body { + display: table-cell; + vertical-align: middle; +} diff --git a/webui/index.html b/webui/index.html new file mode 100644 index 0000000..923f1ed --- /dev/null +++ b/webui/index.html @@ -0,0 +1,113 @@ + + + + + + 3D Drucker + + + + +
+ + + +
+
+

3D Drucker

+ +

Schalter

+ + + + + + + + + +
Strom: + +
Daten: + +
+
+ +