Compare commits

...

2 Commits

Author SHA1 Message Date
Thomas Klaehn
35db2c1f58 Add camera control
Signed-off-by: Thomas Klaehn <thomas.klaehn@perinet.io>
2025-09-11 08:47:20 +02:00
Thomas Klaehn
355ee97c17 Reorder port usage
Signed-off-by: Thomas Klaehn <thomas.klaehn@perinet.io>
2025-09-11 08:10:59 +02:00
7 changed files with 147 additions and 41 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
bin/
build/

View File

@@ -16,7 +16,7 @@ all: service
.PHONY: service .PHONY: service
service: service:
mkdir -p bin mkdir -p build/bin
cd src && go build -o ../$(BIN_FILE) cd src && go build -o ../$(BIN_FILE)
.PHONY: clean .PHONY: clean

View File

@@ -5,7 +5,7 @@ services:
image: octoprint/octoprint image: octoprint/octoprint
restart: unless-stopped restart: unless-stopped
ports: ports:
- 80:80 - 8082:80
devices: devices:
# use `python -m serial.tools.miniterm` to see what the name is of the printer, this requires pyserial # use `python -m serial.tools.miniterm` to see what the name is of the printer, this requires pyserial
- /dev/ttyUSB0:/dev/ttyUSB0 - /dev/ttyUSB0:/dev/ttyUSB0

View File

@@ -0,0 +1,98 @@
package apiservice_camera
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 = 2
)
var (
logger log.Logger = *log.Default()
)
func init() {
logger.SetFlags(log.Llongfile | log.Ltime)
}
func AddHandler() {
http.HandleFunc("GET /camera/state", handle_get_powerstate)
http.HandleFunc("PATCH /camera/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)
}

View File

@@ -6,6 +6,7 @@ import (
"io" "io"
"log" "log"
"net/http" "net/http"
apiservice_camera "powerswitch/internal/apiservice/camera"
apiservice_data "powerswitch/internal/apiservice/data" apiservice_data "powerswitch/internal/apiservice/data"
apiservice_power "powerswitch/internal/apiservice/power" apiservice_power "powerswitch/internal/apiservice/power"
"powerswitch/internal/app/process/octoprint" "powerswitch/internal/app/process/octoprint"
@@ -85,21 +86,32 @@ func handle_patch_printerstate(w http.ResponseWriter, r *http.Request) {
return return
} }
new_state := false
if state.State == StateOn {
new_state = true
}
err = apiservice_data.SetState(new_state)
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(new_state)
if err != nil {
logger.Print(err)
w.WriteHeader(http.StatusInternalServerError)
w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error())))
return
}
err = apiservice_camera.SetState(new_state)
if err != nil {
logger.Print(err)
w.WriteHeader(http.StatusInternalServerError)
w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error())))
return
}
if state.State == StateOn { 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() err = octoprint.ReStart()
if err != nil { if err != nil {
logger.Print(err) logger.Print(err)
@@ -115,20 +127,6 @@ func handle_patch_printerstate(w http.ResponseWriter, r *http.Request) {
w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) w.Write(json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, err.Error())))
return 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) w.WriteHeader(http.StatusOK)
} }

View File

@@ -5,6 +5,7 @@ import (
"log" "log"
"net/http" "net/http"
apiservice_camera "powerswitch/internal/apiservice/camera"
apiservice_data "powerswitch/internal/apiservice/data" apiservice_data "powerswitch/internal/apiservice/data"
apiservice_power "powerswitch/internal/apiservice/power" apiservice_power "powerswitch/internal/apiservice/power"
apiservice_printer "powerswitch/internal/apiservice/printer" apiservice_printer "powerswitch/internal/apiservice/printer"
@@ -29,12 +30,13 @@ func main() {
host.Init() host.Init()
apiservice_camera.AddHandler()
apiservice_data.AddHandler() apiservice_data.AddHandler()
apiservice_power.AddHandler() apiservice_power.AddHandler()
apiservice_printer.AddHandler() apiservice_printer.AddHandler()
apiservice_relay.AddHandler() apiservice_relay.AddHandler()
port := ":5005" port := ":80"
http.Handle("/", http.FileServer(http.Dir(webui_path))) http.Handle("/", http.FileServer(http.Dir(webui_path)))
logger.Fatal(http.ListenAndServe(port, nil)) logger.Fatal(http.ListenAndServe(port, nil))
} }

View File

@@ -7,17 +7,20 @@
<script type="text/javaScript"> <script type="text/javaScript">
const POWER_STATE_URL = "/power/state"; const POWER_STATE_URL = "/power/state";
const DATA_STATE_URL = "/data/state"; const DATA_STATE_URL = "/data/state";
const CAMERA_STATE_URL = "/camera/state";
const PRINTER_STATE_URL = "/printer/state"; const PRINTER_STATE_URL = "/printer/state";
function init() { function init() {
get_state(PRINTER_STATE_URL, "printer_slider"); get_state(PRINTER_STATE_URL, "printer_slider");
get_state(POWER_STATE_URL, "power_slider"); get_state(POWER_STATE_URL, "power_slider");
get_state(DATA_STATE_URL, "data_slider"); get_state(DATA_STATE_URL, "data_slider");
get_state(CAMERA_STATE_URL, "camera_slider");
setInterval(poll, 1000); setInterval(poll, 1000);
} }
function poll() { function poll() {
get_state(PRINTER_STATE_URL, "printer_slider"); get_state(PRINTER_STATE_URL, "printer_slider");
get_state(POWER_STATE_URL, "power_slider"); get_state(POWER_STATE_URL, "power_slider");
get_state(DATA_STATE_URL, "data_slider"); get_state(DATA_STATE_URL, "data_slider");
get_state(CAMERA_STATE_URL, "camera_slider");
} }
function get_state(url, target) { function get_state(url, target) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
@@ -64,20 +67,14 @@
url = POWER_STATE_URL; url = POWER_STATE_URL;
} else if(name === "data") { } else if(name === "data") {
url = DATA_STATE_URL; url = DATA_STATE_URL;
} else if(name === "printer") { } else if(name === "camera") {
url = CAMERA_STATE_URL;
}else if(name === "printer") {
url = PRINTER_STATE_URL; url = PRINTER_STATE_URL;
} }
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open("PATCH", url); xhr.open("PATCH", url);
xhr.setRequestHeader("Content-type", "application/json; charset=utf-8"); 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); xhr.send(obj);
} }
</script> </script>
@@ -124,6 +121,15 @@
</label> </label>
</td> </td>
</tr> </tr>
<tr>
<td>Kamera:</td>
<td>
<label class="switch">
<input id="camera_slider" type="checkbox" onchange="check(this, 'camera')">
<span class="slider round"></span>
</label>
</td>
</tr>
</table> </table>
</div> </div>
<div id="containerdiv"> <div id="containerdiv">
@@ -138,7 +144,7 @@
<td id="container_status"></td> <td id="container_status"></td>
</tr> </tr>
</table> </table>
<a href="http://p4:80" target="_blank">Octoprint</a> <a href="http://p4:8082" target="_blank">Octoprint</a>
<br> <br>
<a href="http://p4:8080" target="_blank">Camera</a> <a href="http://p4:8080" target="_blank">Camera</a>
</div> </div>