Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Missing parameters for codesend #5

Merged
merged 11 commits into from
Jan 7, 2023
Merged
3 changes: 1 addition & 2 deletions .github/workflows/publish_dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ on:
paths-ignore:
- '**.md'
- '.gitignore'
- '.github/**'
- '.doc/**'

jobs:
Expand All @@ -41,7 +40,7 @@ jobs:
run: echo "::set-output name=date::$(date +'%Y.%m.%d')"

- name: Pull image to leverage cache
run: docker pull "$IMAGE_NAME:DEV-latest"
run: docker pull "$IMAGE_NAME:DEV-latest" || true

- name: Build the tagged Docker image
run: >
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3-slim-buster
FROM python:3.11.0b3-slim-buster

LABEL authors="Pascal Höhnel"

Expand All @@ -21,7 +21,7 @@ RUN export BUILD_TOOLS="git make gcc g++" && export WIRINGPI_SUDO="" \
&& cd /opt/wiringPi && rm -rf .git && ./build \
&& git clone --recursive https://github.com/ninjablocks/433Utils.git /opt/433Utils \
&& cd /opt/433Utils \
&& git reset --hard "31c0ea4e158287595a6f6116b6151e72691e1839" \
&& git reset --hard "755cec14a7e16604f214beef2dcad8dbd09de324" \
&& rm -rf .git && cd "RPi_utils" && make all \
&& apt purge -y $BUILD_TOOLS && apt-get autoremove -y\
&& rm -rf /tmp/* /var/lib/apt/lists/* \
Expand Down
59 changes: 56 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ The project is an API-Wrapper around the famous [443Utils](https://github.com/ni
+ [docker run](#docker-run)
+ [docker-compose](#docker-compose)
- [curl request example](#curl-request-example)
- [OpenHAB integration example](#openhab-integration-example)
- [OpenHAB integration example for the `send` binary](#openhab-integration-example-for-the--send--binary)
- [OpenHAB integration example for the `codesend` binary](#openhab-integration-example-for-the--codesend--binary)
- [Security considerations](#security-considerations)
- [Versioning & docker tags](#versioning---docker-tags)
- [Contribution](#contribution)
Expand Down Expand Up @@ -68,10 +69,13 @@ curl http://127.0.0.1:4242/send \

curl http://127.0.0.1:4242/codesend \
--data-urlencode "api_key=<key from docker logs>" \
--data-urlencode "decimalcode=500000"
--data-urlencode "decimalcode=500000" \
--data-urlencode "protocol=optional" \
--data-urlencode "pulselength=optional" \
--data-urlencode "bitlength=optional"
```

## OpenHAB integration example
## OpenHAB integration example for the `send` binary

In this example, new devices can be added by simply adding a switch to the group `gREST_light`, whichs name is in the format
`example_<SYSTEM_CODE>_<UNIT_CODE>`.
Expand Down Expand Up @@ -123,6 +127,55 @@ end

```

## OpenHAB integration example for the `codesend` binary

In this example, new devices can be added by simply adding a switch to the group `gREST_light`, whichs name is in the format
`example_<DECIMALCODE FOR ON>_<DECIMALCODE FOR OFF>`.

The included rule receives the values from the item name, as soon as any item in the group is triggered.

__RESTlight.items__
```
Group:Switch:OR(OFF, ON) gREST_light "REST-light" <light> ["Location"]

Switch RESTLight_10000_1 "Light" <light> (mygroup, gREST_light) ["Switch"]
Switch RESTLight_01000_3 "Light2" <light> (mygroup, gREST_light) ["Switch"]
Switch RESTLight_00100_3 "Light3" <light> (mygroup, gREST_light) ["Switch"]
```

__RESTlight.rules__
```
rule "REST_light"
when
Member of gREST_light received command
then
logInfo("REST_light", "Member " + triggeringItem.name + " to " + receivedCommand)

try {
// receive decimal codes from item name, e.g. zap_1234567_2345678_key1
var decimal_code = ""
if(receivedCommand == ON) {
decimal_code = triggeringItem.name.toString.split("_").get(1)
} if(receivedCommand == OFF) {
decimal_code = triggeringItem.name.toString.split("_").get(2)
}

var protocol = "1"
var pulselength = "150"

var String jsonstring = ('{"api_key" : "<INSERT KEY HERE>", "decimalcode" : "' + decimal_code + '", "protocol" : "' + protocol + '", "pulselength" : "' + pulselength + '"}')

sendHttpPostRequest("http://<INSERT IP HERE>:4242/codesend", "application/json", jsonstring.toString, 5000)
logInfo("REST_light", jsonstring.toString)
logInfo("REST_light", "Finished command!")

} catch(Throwable t) {
logInfo("REST_light", "Caught exception during attempt to contact REST_light API!")
}
end

```

## Security considerations

Although this project was developed with current security best-practices in mind, it is still built around software
Expand Down
45 changes: 27 additions & 18 deletions rest-light.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#!/usr/bin/env python3
##################################################
# Imports & Global variables
##################################################
from flask import Flask, request
import random
import sys
import subprocess
import os
import logging
import re
import string
import subprocess
import sys
import random
from flask import Flask, request

app = Flask(__name__)
DEBUG_MODE = False
Expand Down Expand Up @@ -85,11 +86,11 @@ def load_key():
# function that cleans input from possible injections
def sanitize_input(input):
output = None
try:
try:
output = re.findall("\w+", str(input))[0]
except BaseException as e:
logging.error('Received unparsable web-request')
logging.error(str(e))
logging.error('Received unparsable web-request')
logging.error(str(e))
return output

# function to check, if a provided api-key is valid
Expand All @@ -106,7 +107,7 @@ def check_access(input_args):
return (False, {'error': 'No API-Key provided'})

# function to reveive arguments from request
def parse_request(request, required_arguments):
def parse_request(request, required_arguments, optional_arguments = []):
input_args = None
if request.is_json:
input_args = request.get_json()
Expand All @@ -119,16 +120,16 @@ def parse_request(request, required_arguments):
return (valid, error)

arguments = {}
for argument in required_arguments:
for argument in required_arguments + optional_arguments:
if argument in input_args:
arguments[argument] = sanitize_input(input_args[argument])
else:
elif argument not in input_args and argument in required_arguments:
logging.info('API-Request without mandory field ' + argument)
return (False, {'error': 'Mandatory field ' + argument + ' not provided'})

return (True, arguments)

# function that runs a OS-Subprocess and generates a return-dict
# function that runs a OS-Subprocess and generates a return-dict
def run_command(arguments):
# Run Command and capture output
run_result = None
Expand All @@ -137,14 +138,17 @@ def run_command(arguments):
except subprocess.SubprocessError as e:
logging.fatal(
"Running of subprocess resulted in SubprocessError: " + str(e.output))
return {'status': 'Error', 'stdout': "Running of subprocess resulted in SubprocessError: " + str(e.output)}
return {'status': 'Error',
'stdout': "Running of subprocess resulted in SubprocessError: " + str(e.output)}
except FileNotFoundError as e:
logging.fatal(
"Running of subprocess resulted in FileNotFoundError: " + str(e.strerror))
return {'status': 'Error', 'stdout': "Running of subprocess resulted in FileNotFoundError: " + str(e.strerror)}
return {'status': 'Error',
'stdout': "Running of subprocess resulted in FileNotFoundError: " + str(e.strerror)}
except BaseException as e:
logging.fatal('Unkown exception when trying to run subprocess! ' + str(e))
return {'status': 'Error', 'stdout': 'Unkown exception when trying to run subprocess! ' + str(e)}
return {'status': 'Error',
'stdout': 'Unkown exception when trying to run subprocess! ' + str(e)}

# treat output
try:
Expand All @@ -154,13 +158,15 @@ def run_command(arguments):
elif run_result is not None and hasattr(run_result, 'stderr'):
logging.error(
"Running of command " + " ".join(arguments) + " failed with output: " + str(run_result.stderr))
return {'status': 'Error', 'stdout': str(run_result.stdout), 'stdout': str(run_result.stderr) }
return {'status': 'Error',
'stdout': str(run_result.stdout), 'stderr': str(run_result.stderr) }
else:
logging.error("Running of command " + " ".join(arguments) + " failed without output")
return {'status': 'Error', 'stdout': 'Could not run command!'}
except BaseException as e:
logging.fatal('Unkown exception when trying to parse command " + " ".join(arguments) + " output! ' + str(e))
return {'status': 'Error', 'stdout': 'Unkown exception when trying to parse subprocess output! ' + str(e)}
return {'status': 'Error',
'stdout': 'Unkown exception when trying to parse subprocess output! ' + str(e)}

##################################################
# Flask routes
Expand All @@ -187,12 +193,15 @@ def send():
@app.route('/codesend', methods=['POST'])
def codesend():
request_valid, parsed_request = parse_request(
request, ['decimalcode'])
request, ['decimalcode'], ['protocol', 'pulselength', 'bitlength'])
if not request_valid:
return parsed_request

return run_command([paths['433utils'] + "/codesend",
parsed_request['decimalcode']])
parsed_request['decimalcode'],
parsed_request['protocol'] if 'protocol' in parsed_request else "",
parsed_request['pulselength'] if 'pulselength' in parsed_request else "",
parsed_request['bitlength'] if 'bitlength' in parsed_request else "" ])


##################################################
Expand Down