Compare commits
1 commit
main
...
renovate/c
Author | SHA1 | Date | |
---|---|---|---|
Renovate | f20b0fbfa9 |
58
README.md
58
README.md
|
@ -3,7 +3,7 @@
|
||||||
`spaceapid` serves a [SpaceAPI](https://spaceapi.io)-compatible JSON on port 8080:
|
`spaceapid` serves a [SpaceAPI](https://spaceapi.io)-compatible JSON on port 8080:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ curl http://localhost:8080 | jq
|
$ curl http://[::1]:8080 | jq
|
||||||
{
|
{
|
||||||
"api_compatibility": [
|
"api_compatibility": [
|
||||||
"14"
|
"14"
|
||||||
|
@ -28,50 +28,38 @@ The config consists of three parts:
|
||||||
|
|
||||||
See [Running](#Running) for details.
|
See [Running](#Running) for details.
|
||||||
|
|
||||||
|
## Updating values
|
||||||
|
|
||||||
|
The state of the boolean `state->open` property can be modified via `/state/open`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -X PUT -u user:password -d true http://[::1]:8080/state/open
|
||||||
|
```
|
||||||
|
|
||||||
|
The same is true for the endpoints for sensors configured under `"dynamic"`.
|
||||||
|
Currently only the sensors with the `value/unit/location/name/description` schema are implemented.
|
||||||
|
At the time of writing this includes `temperature`, `barometer`, `humidity`, `beverage_supply`, `power_consumption`,
|
||||||
|
and `account_balance`.
|
||||||
|
Out-of-spec sensors may also be used as long as they share the same schema.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -X PUT -u user:password -d 23.42 http://[::1]:8080/sensors/{temperature,humidity,...}[/location[/name]]
|
||||||
|
```
|
||||||
|
|
||||||
|
As can be seen in the example, the http urls are generated from sensor type and optionally `location` and `name`.
|
||||||
|
Depending on sensor type `location` might be required for your sensors, see the schema for details.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
See the `go.mod` file for minimum required Go version.
|
See the `go.mod` file for minimum required Go version.
|
||||||
There are currently no dependencies apart from the Go standard library.
|
There are currently no dependencies apart from the Go standard library.
|
||||||
|
|
||||||
``` shell
|
|
||||||
go build -ldflags '-X main.version=v420.69-rc23' .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
Set the environment variable to a comma-separated list of config files or pass the `-c` flag.
|
Set the environment variable to a comma-separated list of config files or pass the `-c` flag.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
env SPACEAPID_CONFIG_PATH=config-template.json go run .
|
env CONFIG_PATH=config-template.json go run .
|
||||||
# OR
|
# OR
|
||||||
go run . -c config-credentials.json,config-dynamic.json,config-response.json
|
go run . -c config-credentials.json,config-dynamic.json,config-response.json
|
||||||
```
|
```
|
||||||
|
|
||||||
## Updating values
|
|
||||||
|
|
||||||
The state of the boolean `state->open` and `state->message` property can be modified via `/state/{open,message}`:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
curl -X PUT -u user:password -d true http://localhost:8080/state/open
|
|
||||||
curl -X PUT -u user:password -d "Nur mit Passierschein A38 :3" http://localhost:8080/state/message
|
|
||||||
```
|
|
||||||
|
|
||||||
As `state->message` is optional, its value can be deleted by using the `PUT` method with an empty payload, or by using `DELETE`:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
curl -X PUT -u user:password -d "" http://localhost:8080/state/message
|
|
||||||
# OR
|
|
||||||
curl -X DELETE -u user:password http://localhost:8080/state/message
|
|
||||||
```
|
|
||||||
|
|
||||||
The same updating procedure applies for the endpoints for sensors configured under `"dynamic"`.
|
|
||||||
Currently only the sensors with the `value/unit/location/name/description` schema are implemented.
|
|
||||||
At the time of writing this includes `temperature`, `barometer`, `humidity`, `beverage_supply`, `power_consumption`, and `account_balance`.
|
|
||||||
Out-of-spec sensors may be used as well, as long as they share the same schema.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
curl -X PUT -u user:password -d 23.42 http://localhost:8080/sensors/{temperature,humidity,...}[/location[/name]]
|
|
||||||
```
|
|
||||||
|
|
||||||
As can be seen in the example, the http urls are generated from sensor type and optionally `location` and `name`.
|
|
||||||
Depending on sensor type, `location` might be required for your sensors, see the schema for details.
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
envConfigPath = "SPACEAPID_CONFIG_PATH"
|
envConfigPath = "CONFIG_PATH"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pathsT []string
|
type pathsT []string
|
||||||
|
@ -59,7 +59,7 @@ func getConfigPaths() (paths pathsT) {
|
||||||
|
|
||||||
// If paths is still empty we are missing something
|
// If paths is still empty we are missing something
|
||||||
if len(paths) == 0 {
|
if len(paths) == 0 {
|
||||||
log.Fatalln("Error: Neither env: ", envConfigPath, "nor cli flag was specified, unable to start without config.")
|
log.Fatalln("Neither the env variable nor cli flag was specified, we are missing a config file.")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ func ParseConfiguration() (conf SpaceapidConfig) {
|
||||||
log.Fatalln("Provided file doesn't specify compatibility with API version 14")
|
log.Fatalln("Provided file doesn't specify compatibility with API version 14")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize fields for environment sensors
|
// Initialise fields for environment sensors
|
||||||
conf.Response.Sensors = make(map[string][]types.EnvironmentSensor)
|
conf.Response.Sensors = make(map[string][]types.EnvironmentSensor)
|
||||||
for key, sensorConfigs := range conf.Dynamic.Sensors {
|
for key, sensorConfigs := range conf.Dynamic.Sensors {
|
||||||
conf.Response.Sensors[key] = make([]types.EnvironmentSensor, len(sensorConfigs))
|
conf.Response.Sensors[key] = make([]types.EnvironmentSensor, len(sensorConfigs))
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,3 +1,3 @@
|
||||||
module git.hamburg.ccc.de/ccchh/spaceapid
|
module git.hamburg.ccc.de/ccchh/spaceapid
|
||||||
|
|
||||||
go 1.22
|
go 1.21
|
||||||
|
|
|
@ -34,37 +34,3 @@ func StateOpen(
|
||||||
resp.LastChange = time.Now().Unix()
|
resp.LastChange = time.Now().Unix()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func StateMessagePUT(
|
|
||||||
authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
|
|
||||||
resp *types.SpaceState,
|
|
||||||
) func(http.ResponseWriter, *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
body, err := updateEndpointValidator(authDB, validCredentials, w, r)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set SpaceAPI response values
|
|
||||||
resp.Message = string(body)
|
|
||||||
resp.LastChange = time.Now().Unix()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func StateMessageDELETE(
|
|
||||||
authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
|
|
||||||
resp *types.SpaceState,
|
|
||||||
) func(http.ResponseWriter, *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_, err := updateEndpointValidator(authDB, validCredentials, w, r)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set SpaceAPI response values
|
|
||||||
resp.Message = ""
|
|
||||||
resp.LastChange = time.Now().Unix()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ import (
|
||||||
"git.hamburg.ccc.de/ccchh/spaceapid/util"
|
"git.hamburg.ccc.de/ccchh/spaceapid/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// updateEndpointValidator checks BasicAuth credentials and then returns the request body
|
// updateEndpointValidator checks BasicAuth credentials,
|
||||||
|
// checks for correct HTTP method and then returns the request body
|
||||||
func updateEndpointValidator(
|
func updateEndpointValidator(
|
||||||
authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
|
authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
|
||||||
w http.ResponseWriter, r *http.Request,
|
w http.ResponseWriter, r *http.Request,
|
||||||
|
@ -23,6 +24,13 @@ func updateEndpointValidator(
|
||||||
return []byte{}, errors.New(fmt.Sprintf("Unauthorized request from %s Username: %s Password: %s", r.RemoteAddr, username, password))
|
return []byte{}, errors.New(fmt.Sprintf("Unauthorized request from %s Username: %s Password: %s", r.RemoteAddr, username, password))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if PUT method
|
||||||
|
if r.Method != http.MethodPut {
|
||||||
|
w.Header().Set("Allow", http.MethodPut)
|
||||||
|
http.Error(w, "", http.StatusMethodNotAllowed)
|
||||||
|
return []byte{}, errors.New(fmt.Sprintf("Wrong Method: %s from %s at %s", r.Method, r.RemoteAddr, r.RequestURI))
|
||||||
|
}
|
||||||
|
|
||||||
// Read request body
|
// Read request body
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
20
main.go
20
main.go
|
@ -50,29 +50,21 @@ func main() {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}(&conf.Response)
|
}(&conf.Response)
|
||||||
|
|
||||||
// Root handler
|
// Register HTTP handlers
|
||||||
http.HandleFunc("GET /{$}",
|
http.HandleFunc("/",
|
||||||
handlers.Root(&conf.Response),
|
handlers.Root(&conf.Response),
|
||||||
)
|
)
|
||||||
// state->open
|
http.HandleFunc("/state/open",
|
||||||
http.HandleFunc("PUT /state/open",
|
|
||||||
handlers.StateOpen(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State),
|
handlers.StateOpen(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State),
|
||||||
)
|
)
|
||||||
// state->message
|
// Register handlers for Environmental Sensors
|
||||||
http.HandleFunc("PUT /state/message",
|
|
||||||
handlers.StateMessagePUT(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State),
|
|
||||||
)
|
|
||||||
http.HandleFunc("DELETE /state/message",
|
|
||||||
handlers.StateMessageDELETE(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State),
|
|
||||||
)
|
|
||||||
// Register handlers for environmental sensors
|
|
||||||
for sensorType, envSensorConfigs := range conf.Dynamic.Sensors {
|
for sensorType, envSensorConfigs := range conf.Dynamic.Sensors {
|
||||||
for i, envSensorConfig := range envSensorConfigs {
|
for i, envSensorConfig := range envSensorConfigs {
|
||||||
urlPattern := "PUT " + util.GetSensorURLPath(
|
urlPath := util.GetSensorURLPath(
|
||||||
sensorType, envSensorConfig.SensorData.Location, envSensorConfig.SensorData.Name,
|
sensorType, envSensorConfig.SensorData.Location, envSensorConfig.SensorData.Name,
|
||||||
)
|
)
|
||||||
http.HandleFunc(
|
http.HandleFunc(
|
||||||
urlPattern,
|
urlPath,
|
||||||
handlers.EnvironmentSensor(
|
handlers.EnvironmentSensor(
|
||||||
conf.Credentials, envSensorConfig.AllowedCredentials, &conf.Response.Sensors[sensorType][i],
|
conf.Credentials, envSensorConfig.AllowedCredentials, &conf.Response.Sensors[sensorType][i],
|
||||||
),
|
),
|
||||||
|
|
|
@ -89,10 +89,9 @@ func SaveCurrentState(response types.SpaceAPIResponseV14) {
|
||||||
// Create persistent state
|
// Create persistent state
|
||||||
persistentStateV14 := types.PersistentStateV14{
|
persistentStateV14 := types.PersistentStateV14{
|
||||||
State: struct {
|
State: struct {
|
||||||
Open bool `json:"open"`
|
Open bool `json:"open"`
|
||||||
LastChange int64 `json:"lastchange"`
|
LastChange int64 `json:"lastchange"`
|
||||||
Message string `json:"message,omitempty"`
|
}{Open: response.State.Open, LastChange: response.State.LastChange},
|
||||||
}{Open: response.State.Open, LastChange: response.State.LastChange, Message: response.State.Message},
|
|
||||||
}
|
}
|
||||||
// Save sensor state
|
// Save sensor state
|
||||||
persistentStateV14.Sensors = make(map[string][]types.PersistentEnvironmentSensor)
|
persistentStateV14.Sensors = make(map[string][]types.PersistentEnvironmentSensor)
|
||||||
|
|
6
renovate.json
Normal file
6
renovate.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended"
|
||||||
|
]
|
||||||
|
}
|
10
types/v14.go
10
types/v14.go
|
@ -38,9 +38,8 @@ type SpaceAPIResponseV14 struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type SpaceState struct {
|
type SpaceState struct {
|
||||||
Open bool `json:"open"`
|
Open bool `json:"open"`
|
||||||
LastChange int64 `json:"lastchange"`
|
LastChange int64 `json:"lastchange"`
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnvironmentSensor struct {
|
type EnvironmentSensor struct {
|
||||||
|
@ -54,9 +53,8 @@ type EnvironmentSensor struct {
|
||||||
|
|
||||||
type PersistentStateV14 struct {
|
type PersistentStateV14 struct {
|
||||||
State struct {
|
State struct {
|
||||||
Open bool `json:"open"`
|
Open bool `json:"open"`
|
||||||
LastChange int64 `json:"lastchange"`
|
LastChange int64 `json:"lastchange"`
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
} `json:"state"`
|
} `json:"state"`
|
||||||
Sensors map[string][]PersistentEnvironmentSensor `json:"sensors,omitempty"`
|
Sensors map[string][]PersistentEnvironmentSensor `json:"sensors,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue