16
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										61
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -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/*
 | 
				
			||||||
							
								
								
									
										40
									
								
								config/octoprint/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								config/octoprint/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							@@ -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:
 | 
				
			||||||
							
								
								
									
										10
									
								
								printctrl.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								printctrl.service
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
							
								
								
									
										37
									
								
								src/app/process/octoprint/octoprint.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/app/process/octoprint/octoprint.go
									
									
									
									
									
										Normal file
									
								
							@@ -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()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										77
									
								
								src/app/process/process.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/app/process/process.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/go.mod
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/go.sum
									
									
									
									
									
										Normal file
									
								
							@@ -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=
 | 
				
			||||||
							
								
								
									
										98
									
								
								src/internal/apiservice/data/data.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/internal/apiservice/data/data.go
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										98
									
								
								src/internal/apiservice/power/power.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/internal/apiservice/power/power.go
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										132
									
								
								src/internal/apiservice/printer/printer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/internal/apiservice/printer/printer.go
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										130
									
								
								src/internal/apiservice/relay/relay.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								src/internal/apiservice/relay/relay.go
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								src/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/main.go
									
									
									
									
									
										Normal file
									
								
							@@ -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))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										61
									
								
								webui/css/slider.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								webui/css/slider.css
									
									
									
									
									
										Normal file
									
								
							@@ -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%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								webui/css/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								webui/css/style.css
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										113
									
								
								webui/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								webui/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					    <head>
 | 
				
			||||||
 | 
					        <link rel="stylesheet" href="css/style.css">
 | 
				
			||||||
 | 
					        <link rel="stylesheet" href="css/slider.css">
 | 
				
			||||||
 | 
					        <title id="title">3D Drucker</title>
 | 
				
			||||||
 | 
					        <script type="text/javaScript">
 | 
				
			||||||
 | 
					            const POWER_STATE_URL = "/power/state";
 | 
				
			||||||
 | 
					            const DATA_STATE_URL = "/data/state";
 | 
				
			||||||
 | 
					            const PRINTER_STATE_URL = "/printer/state";
 | 
				
			||||||
 | 
					            function init() {
 | 
				
			||||||
 | 
					                get_state(PRINTER_STATE_URL, "printer_slider");
 | 
				
			||||||
 | 
					                get_state(POWER_STATE_URL, "power_slider");
 | 
				
			||||||
 | 
					                get_state(DATA_STATE_URL, "data_slider");
 | 
				
			||||||
 | 
					                setInterval(poll, 1000);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            function poll() {
 | 
				
			||||||
 | 
					                get_state(PRINTER_STATE_URL, "printer_slider");
 | 
				
			||||||
 | 
					                get_state(POWER_STATE_URL, "power_slider");
 | 
				
			||||||
 | 
					                get_state(DATA_STATE_URL, "data_slider");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            function get_state(url, target) {
 | 
				
			||||||
 | 
					                const xhr = new XMLHttpRequest();
 | 
				
			||||||
 | 
					                xhr.open("GET", url);
 | 
				
			||||||
 | 
					                xhr.send();
 | 
				
			||||||
 | 
					                xhr.responseType = "json";
 | 
				
			||||||
 | 
					                xhr.onload = () => {
 | 
				
			||||||
 | 
					                    if (xhr.readyState == 4 && xhr.status == 200) {
 | 
				
			||||||
 | 
					                        let state = xhr.response
 | 
				
			||||||
 | 
					                        let btn = document.getElementById(target);
 | 
				
			||||||
 | 
					                        let chk = false;
 | 
				
			||||||
 | 
					                        if(state.state === "on") {
 | 
				
			||||||
 | 
					                            chk = true;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        btn.checked = chk;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        console.log(`Error: ${xhr.status}`);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            function check(checkbox, name) {
 | 
				
			||||||
 | 
					                var obj;
 | 
				
			||||||
 | 
					                var url;
 | 
				
			||||||
 | 
					                if(checkbox.checked) {
 | 
				
			||||||
 | 
					                    obj = '{"state":"on"}'
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    obj = '{"state":"off"}'
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if(name === "power") {
 | 
				
			||||||
 | 
					                    url = POWER_STATE_URL;
 | 
				
			||||||
 | 
					                } else if(name === "data") {
 | 
				
			||||||
 | 
					                    url = DATA_STATE_URL;
 | 
				
			||||||
 | 
					                } else if(name === "printer") {
 | 
				
			||||||
 | 
					                    url = PRINTER_STATE_URL;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const xhr = new XMLHttpRequest();
 | 
				
			||||||
 | 
					                xhr.open("PATCH", url);
 | 
				
			||||||
 | 
					                xhr.setRequestHeader("Content-type", "application/json; charset=utf-8");
 | 
				
			||||||
 | 
					                // xhr.onload = () => {
 | 
				
			||||||
 | 
					                //     var data = JSON.parse(xhr.responseText);
 | 
				
			||||||
 | 
					                //     if (xhr.readyState == 4 && xhr.status == "202") {
 | 
				
			||||||
 | 
					                //         console.log(data);
 | 
				
			||||||
 | 
					                //     } else {
 | 
				
			||||||
 | 
					                //         console.log(`Error: ${xhr.status}`);
 | 
				
			||||||
 | 
					                //     }
 | 
				
			||||||
 | 
					                // };
 | 
				
			||||||
 | 
					                xhr.send(obj);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        </script>
 | 
				
			||||||
 | 
					    </head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <body onload=init()>
 | 
				
			||||||
 | 
					        <div class="headercontainer">
 | 
				
			||||||
 | 
					            <!-- <div class="meta">
 | 
				
			||||||
 | 
					                <div class="left" style="line-height: 24px;font-weight: bold;" id="headline">Wasser</div>
 | 
				
			||||||
 | 
					                <div class="middle"></div>
 | 
				
			||||||
 | 
					                <div class="right"><span></span></div>
 | 
				
			||||||
 | 
					            </div> -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- <div class="header" id="header_cnt">
 | 
				
			||||||
 | 
					                <div class="headerlogo"><img src="images/logo_perinet.png" width="258" height="94" alt="" /></div>
 | 
				
			||||||
 | 
					            </div> -->
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div id="content">
 | 
				
			||||||
 | 
					            <h1>3D Drucker</h1>
 | 
				
			||||||
 | 
					                <label class="switch">
 | 
				
			||||||
 | 
					                    <input id="printer_slider" type="checkbox" onchange="check(this, 'printer')">
 | 
				
			||||||
 | 
					                    <span class="slider round"></span>
 | 
				
			||||||
 | 
					                </label>
 | 
				
			||||||
 | 
					            <h2>Schalter</h2>
 | 
				
			||||||
 | 
					            <table>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <td>Strom:</td>
 | 
				
			||||||
 | 
					                    <td>
 | 
				
			||||||
 | 
					                        <label class="switch">
 | 
				
			||||||
 | 
					                            <input id="power_slider" type="checkbox" onchange="check(this, 'power')">
 | 
				
			||||||
 | 
					                            <span class="slider round"></span>
 | 
				
			||||||
 | 
					                        </label>
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <td>Daten:</td>
 | 
				
			||||||
 | 
					                    <td>
 | 
				
			||||||
 | 
					                        <label class="switch">
 | 
				
			||||||
 | 
					                            <input id="data_slider" type="checkbox" onchange="check(this, 'data')">
 | 
				
			||||||
 | 
					                            <span class="slider round"></span>
 | 
				
			||||||
 | 
					                        </label>
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					            </table>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
		Reference in New Issue
	
	Block a user