2023-01-28 23:28:37 +02:00
package main
import (
2023-02-02 11:44:30 +02:00
"embed"
2023-01-29 23:31:21 +02:00
"encoding/json"
2023-03-03 10:09:43 +02:00
"flag"
2023-02-02 11:44:30 +02:00
"io/fs"
2023-01-29 23:31:21 +02:00
"log"
"net/http"
2023-03-03 10:09:43 +02:00
"os"
2023-02-24 22:52:13 +02:00
"strconv"
"strings"
2023-03-12 12:07:36 +02:00
"time"
2023-01-29 14:42:03 +02:00
2023-01-28 23:28:37 +02:00
"github.com/0ranki/enervent-ctrl/enervent-ctrl-go/pingvinKL"
2023-03-03 10:09:43 +02:00
"github.com/gorilla/handlers"
2023-03-12 12:07:36 +02:00
"github.com/rocketlaunchr/https-go"
2023-01-28 23:28:37 +02:00
)
2023-02-02 13:38:31 +02:00
// Remember to dereference the symbolic links under ./static/html
// prior to building the binary e.g. by using tar
2023-02-02 11:44:30 +02:00
//go:embed static/html/*
var static embed . FS
2023-01-29 23:31:21 +02:00
var (
2023-03-12 12:07:36 +02:00
version = "0.0.18"
2023-03-03 10:09:43 +02:00
pingvin pingvinKL . PingvinKL
DEBUG * bool
INTERVAL * int
ACCESS_LOG * bool
2023-01-29 23:31:21 +02:00
)
func coils ( w http . ResponseWriter , r * http . Request ) {
2023-01-29 23:43:36 +02:00
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2023-02-24 22:52:13 +02:00
pathparams := strings . Split ( strings . TrimPrefix ( r . URL . Path , "/api/v1/coils/" ) , "/" )
if len ( pathparams [ 0 ] ) == 0 {
json . NewEncoder ( w ) . Encode ( pingvin . Coils )
} else if len ( pathparams [ 0 ] ) > 0 && r . Method == "GET" && len ( pathparams ) < 2 { // && r.Method == "POST"
intaddr , err := strconv . Atoi ( pathparams [ 0 ] )
if err != nil {
log . Println ( "ERROR: Could not parse coil address" , pathparams [ 0 ] )
log . Println ( err )
return
}
pingvin . ReadCoil ( uint16 ( intaddr ) )
json . NewEncoder ( w ) . Encode ( pingvin . Coils [ intaddr ] )
} else if len ( pathparams [ 0 ] ) > 0 && r . Method == "POST" && len ( pathparams ) == 2 {
intaddr , err := strconv . Atoi ( pathparams [ 0 ] )
if err != nil {
log . Println ( "ERROR: Could not parse coil address" , pathparams [ 0 ] )
log . Println ( err )
return
}
boolval , err := strconv . ParseBool ( pathparams [ 1 ] )
if err != nil {
log . Println ( "ERROR: Could not parse coil value" , pathparams [ 1 ] )
log . Println ( err )
return
}
pingvin . WriteCoil ( uint16 ( intaddr ) , boolval )
json . NewEncoder ( w ) . Encode ( pingvin . Coils [ intaddr ] )
2023-03-03 08:08:36 +02:00
} else if len ( pathparams [ 0 ] ) > 0 && r . Method == "POST" && len ( pathparams ) == 1 {
intaddr , err := strconv . Atoi ( pathparams [ 0 ] )
if err != nil {
log . Println ( "ERROR: Could not parse coil address" , pathparams [ 0 ] )
log . Println ( err )
return
}
pingvin . WriteCoil ( uint16 ( intaddr ) , ! pingvin . Coils [ intaddr ] . Value )
json . NewEncoder ( w ) . Encode ( pingvin . Coils [ intaddr ] )
2023-02-24 22:52:13 +02:00
}
2023-01-29 23:31:21 +02:00
}
2023-02-01 22:12:56 +02:00
func registers ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2023-03-02 14:24:05 +02:00
pathparams := strings . Split ( strings . TrimPrefix ( r . URL . Path , "/api/v1/registers/" ) , "/" )
if len ( pathparams [ 0 ] ) == 0 {
json . NewEncoder ( w ) . Encode ( pingvin . Registers )
} else if len ( pathparams [ 0 ] ) > 0 && r . Method == "GET" && len ( pathparams ) < 2 { // && r.Method == "POST"
intaddr , err := strconv . Atoi ( pathparams [ 0 ] )
if err != nil {
log . Println ( "ERROR: Could not parse register address" , pathparams [ 0 ] )
log . Println ( err )
return
}
pingvin . ReadRegister ( uint16 ( intaddr ) )
json . NewEncoder ( w ) . Encode ( pingvin . Registers [ intaddr ] )
} else if len ( pathparams [ 0 ] ) > 0 && r . Method == "POST" && len ( pathparams ) == 2 {
intaddr , err := strconv . Atoi ( pathparams [ 0 ] )
if err != nil {
log . Println ( "ERROR: Could not parse register address" , pathparams [ 0 ] )
log . Println ( err )
return
}
intval , err := strconv . Atoi ( pathparams [ 1 ] )
if err != nil {
log . Println ( "ERROR: Could not parse register value" , pathparams [ 1 ] )
log . Println ( err )
return
}
_ , err = pingvin . WriteRegister ( uint16 ( intaddr ) , uint16 ( intval ) )
if err != nil {
log . Println ( err )
}
json . NewEncoder ( w ) . Encode ( pingvin . Registers [ intaddr ] )
2023-02-01 22:19:57 +02:00
}
2023-02-01 22:12:56 +02:00
}
2023-02-18 21:52:53 +02:00
func status ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
json . NewEncoder ( w ) . Encode ( pingvin . Status )
}
2023-03-09 16:15:54 +02:00
func temperature ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
pathparams := strings . Split ( strings . TrimPrefix ( r . URL . Path , "/api/v1/temperature/" ) , "/" )
if len ( pathparams [ 0 ] ) > 0 && r . Method == "POST" && len ( pathparams ) == 1 {
pingvin . Temperature ( pathparams [ 0 ] )
json . NewEncoder ( w ) . Encode ( pingvin . Registers [ 135 ] )
} else {
return
}
}
2023-01-29 23:31:21 +02:00
func listen ( ) {
log . Println ( "Starting pingvinAPI..." )
http . HandleFunc ( "/api/v1/coils/" , coils )
2023-02-01 22:12:56 +02:00
http . HandleFunc ( "/api/v1/registers/" , registers )
2023-02-18 21:52:53 +02:00
http . HandleFunc ( "/api/v1/status" , status )
2023-03-09 16:15:54 +02:00
http . HandleFunc ( "/api/v1/temperature/" , temperature )
2023-02-02 11:44:30 +02:00
html , err := fs . Sub ( static , "static/html" )
if err != nil {
log . Fatal ( err )
}
htmlroot := http . FileServer ( http . FS ( html ) )
http . Handle ( "/" , htmlroot )
2023-03-03 10:09:43 +02:00
if * ACCESS_LOG {
handler := handlers . LoggingHandler ( os . Stdout , http . DefaultServeMux )
err = http . ListenAndServe ( ":8888" , handler )
} else {
err = http . ListenAndServe ( ":8888" , nil )
}
2023-02-01 21:52:55 +02:00
if err != nil {
log . Fatal ( err )
}
2023-01-29 23:31:21 +02:00
}
2023-03-12 12:07:36 +02:00
func generateCertificate ( certpath , cert , key string ) {
if _ , err := os . Stat ( certpath ) ; err != nil {
log . Println ( "Generating configuration directory" , certpath )
if err := os . MkdirAll ( certpath , 0750 ) ; err != nil {
log . Fatal ( "Failed to generate configuration directory:" , err )
}
}
opts := https . GenerateOptions { Host : "enervent-ctrl.local" , RSABits : 4096 , ValidFor : 10 * 365 * 24 * time . Hour }
log . Println ( "Generating new self-signed SSL keypair to " , certpath )
pub , priv , err := https . GenerateKeys ( opts )
if err != nil {
log . Fatal ( "Error generating SSL certificate: " , err )
}
pingvin . Debug . Println ( "Certificate:\n" , string ( pub ) )
pingvin . Debug . Println ( "Key:\n" , string ( priv ) )
if err := os . WriteFile ( key , priv , 0600 ) ; err != nil {
log . Fatal ( "Error writing private key " , key , ": " , err )
}
log . Println ( "Wrote new SSL private key " , cert )
if err := os . WriteFile ( cert , pub , 0644 ) ; err != nil {
log . Fatal ( "Error writing certificate " , cert , ": " , err )
}
log . Println ( "Wrote new SSL public key " , cert )
}
2023-03-03 10:09:43 +02:00
func configure ( ) {
log . Println ( "Reading configuration" )
DEBUG = flag . Bool ( "debug" , false , "Enable debug logging" )
INTERVAL = flag . Int ( "interval" , 4 , "Set the interval of background updates" )
ACCESS_LOG = flag . Bool ( "httplog" , false , "Enable HTTP access logging" )
2023-03-12 12:07:36 +02:00
generatecert := flag . Bool ( "regenerate-certs" , false , "Generate a new SSL certificate. A new one is generated on startup as `~/.config/enervent-ctrl/server.crt` if it doesn't exist." )
// TODO: flag for cerificate path
// TODO: log file flag
2023-03-03 10:09:43 +02:00
flag . Parse ( )
2023-03-12 12:07:36 +02:00
// Get the user home directory path
homedir , err := os . UserHomeDir ( )
if err != nil {
log . Fatal ( "Could not determine user home directory" )
}
certpath := homedir + "/.config/enervent-ctrl/"
cert := certpath + "certificate.pem"
key := certpath + "privatekey.pem"
// Check that certificate file exists
if _ , err = os . Stat ( cert ) ; err != nil || * generatecert {
generateCertificate ( certpath , cert , key )
}
2023-03-03 10:09:43 +02:00
if * DEBUG {
log . Println ( "Debug logging enabled" )
}
if * ACCESS_LOG {
log . Println ( "HTTP Access logging enabled" )
}
log . Println ( "Update interval set to" , * INTERVAL , "seconds" )
}
2023-01-28 23:28:37 +02:00
func main ( ) {
2023-01-29 23:31:21 +02:00
log . Println ( "enervent-ctrl version" , version )
2023-03-03 10:09:43 +02:00
configure ( )
pingvin = pingvinKL . New ( * DEBUG )
2023-01-29 21:56:22 +02:00
pingvin . Update ( )
2023-03-03 10:09:43 +02:00
go pingvin . Monitor ( * INTERVAL )
2023-01-29 23:31:21 +02:00
listen ( )
2023-03-09 22:17:24 +02:00
pingvin . Quit ( )
2023-01-28 23:28:37 +02:00
}