Skip to content

Commit

Permalink
Issues first working version - Needs to be tested on raspi
Browse files Browse the repository at this point in the history
  • Loading branch information
Dennis Muth committed Feb 6, 2018
1 parent b3c7042 commit d402df6
Show file tree
Hide file tree
Showing 22 changed files with 254 additions and 33 deletions.
16 changes: 16 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.git
.idea
.pytest_cache
htmlcov
.coverage


Dockerfile
.DS_Store
.gitignore
.dockerignore
README.md


Makefile
Makefile.docker
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.DS_Store

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
12 changes: 6 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ ENV WORKDIR /rpi-433rc
RUN mkdir -p ${WORKDIR} && \
cd ${WORKDIR}

COPY ./entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# Install necessary dependencies
RUN pip3 install rpi-rf==${RPIRF_VERSION}

# Install rest-api wrapper for rpi-rf
# Not yet implemented
COPY . ${WORKDIR}
RUN pip3 install -r ${WORKDIR}/requirements.txt

# Re-copy the entrypoint.sh to the root
COPY ./entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# Entrypoint defaults to bash
ENTRYPOINT ["/entrypoint.sh"]
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ lint:
flake8 --exclude=.tox --max-line-length 120 $(SOURCE_PATH)

test:
pytest --verbose --color=yes --doctest-modules -s --cov=$(SOURCE_PATH) --cov-report html --cov-report term $(TEST_PATH) $(SOURCE_PATH)
pytest --verbose --color=yes -s \
--doctest-modules \
--cov=$(SOURCE_PATH) --cov-report html --cov-report term \
$(TEST_PATH) $(SOURCE_PATH)

doctest:
pytest --verbose --color=yes --doctest-modules $(SOURCE_PATH)
16 changes: 12 additions & 4 deletions Makefile.docker
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,23 @@ build:
docker build -t $(RELEASE_IMAGE_NAME):$(CURRENT_TAG) .

shell: build
docker run -it --rm --name $(CONTAINER_NAME) --privileged --cap-add SYS_RAWIO --device=/dev/mem $(RELEASE_IMAGE_NAME):$(CURRENT_TAG) bash
docker run -it --rm \
--name $(CONTAINER_NAME) \
--privileged --cap-add SYS_RAWIO --device=/dev/mem \
$(RELEASE_IMAGE_NAME):$(CURRENT_TAG) bash

run: build
docker run --rm --name $(CONTAINER_NAME) --privileged --cap-add SYS_RAWIO --device=/dev/mem $(RELEASE_IMAGE_NAME):$(CURRENT_TAG)
docker run --rm \
--name $(CONTAINER_NAME) \
--privileged --cap-add SYS_RAWIO --device=/dev/mem \
$(RELEASE_IMAGE_NAME):$(CURRENT_TAG)

sniff: build
docker run --rm -it --name $(CONTAINER_NAME) --privileged --cap-add SYS_RAWIO --device=/dev/mem $(RELEASE_IMAGE_NAME):$(CURRENT_TAG) sniff
docker run --rm -it \
--name $(CONTAINER_NAME) \
--privileged --cap-add SYS_RAWIO --device=/dev/mem \
$(RELEASE_IMAGE_NAME):$(CURRENT_TAG) sniff

clean:
docker ps -a | grep '$(CONTAINER_NAME)' | awk '{print $$1}' | xargs docker rm
if [ $(shell docker image inspect $(RELEASE_IMAGE_NAME):$(CURRENT_TAG) > /dev/null 2>/dev/null ; echo $$?) -eq 0 ] ; then docker rmi $(RELEASE_IMAGE_NAME):$(CURRENT_TAG) ; fi

14 changes: 14 additions & 0 deletions conf/devices.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"miffy": {
"code_on": 70997,
"code_off": 12653909
},
"moon": {
"code_on": 333141,
"code_off": 12916053
},
"device3": {
"code_on": 1316181,
"code_off": 13899093
}
}
3 changes: 1 addition & 2 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ case ${1} in
;;
serve)
echo "Serving via rest-api is not implemented"
break
exec ${WORKDIR}/run.sh
;;
*)
exec "$@"
;;
esac

3 changes: 2 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pip-compile
pip-tools
pytest
pytest-cov
pytest-mock
flake8
4 changes: 4 additions & 0 deletions requirements.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
attrs
flask
schema
rpi-rf
19 changes: 16 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
attrs
schema
flask
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --output-file requirements.txt requirements.in
#
attrs==17.4.0
click==6.7 # via flask
flask==0.12.2
itsdangerous==0.24 # via flask
jinja2==2.10 # via flask
markupsafe==1.0 # via jinja2
rpi-rf==0.9.6
rpi.gpio==0.6.3 # via rpi-rf
schema==0.6.7
werkzeug==0.14.1 # via flask
14 changes: 7 additions & 7 deletions rpi433rc/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import os

from flask_restplus import Api

from ..business.devices import DeviceDict, MemoryState, DeviceRegistry
from ..business.rc433 import RC433
from ..config import VERSION, GPIO_OUT
from ..config import VERSION, GPIO_OUT, CONFIG_DIR

api = Api(
title='RPi433',
Expand All @@ -19,11 +21,9 @@
from .send import api as ns_send
api.add_namespace(ns_send)


device_dict = {
'device1': {"code_on": 12345, 'code_off': "23456"},
'device2': {"system_code": "00010", "device_code": "2"}
}
device_db = DeviceRegistry(DeviceDict(device_dict), MemoryState())
config_file = os.path.join(CONFIG_DIR, 'devices.json')
device_store = DeviceDict.from_json(config_file)
device_state = MemoryState()
device_db = DeviceRegistry(device_store, device_state)

rc433 = RC433(gpio_out=GPIO_OUT)
7 changes: 4 additions & 3 deletions rpi433rc/api/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def unknown_device(error):

device = api.model('Device', {
'device_name': fields.String(attribute="device.device_name"),
'type': fields.String(attribute=lambda o: str(o.device.__class__.__name__) if hasattr(o,'device') else None),
'type': fields.String(attribute=lambda o: str(o.device.__class__.__name__) if hasattr(o, 'device') else None),
'configuration': _fields.Dict(attribute="device.configuration"),
'state': _fields.OnOff
})
Expand Down Expand Up @@ -52,7 +52,8 @@ def get(self, device_name, on_off):
from . import device_db
from . import rc433
device = device_db.lookup(device_name)
if rc433.switch_device(device):
res = rc433.switch_device(device, on_off)
if res:
# Mark the device as on resp. off
device_db.switch(device_name, on_off)
return {'state': on_off}
return {'state': on_off, 'result': res}
17 changes: 16 additions & 1 deletion rpi433rc/api/flaskutil/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,24 @@


class OnOffConverter(BaseConverter):
"""
Converts on/off in routing to True/False (e.g. <on_off:switch>.
Has to be registered by calling `app.url_map.converters['on_off'] = OnOffConverter`
Example:
>>> from collections import namedtuple
>>> m = namedtuple("m", ['charset'])
>>> cs = m(charset='utf-8')
>>> dut = OnOffConverter(cs)
>>> dut.to_python('on'), dut.to_python('off'), dut.to_python('blub')
(True, False, False)
>>> dut.to_url(value=True), dut.to_url(value=False)
('on', 'off')
"""

def to_python(self, value):
return value.lower() == 'on'

def to_url(self, value):
return BaseConverter.to_url('on' if value else 'off')
return BaseConverter.to_url(self, value='on' if value else 'off')
7 changes: 7 additions & 0 deletions rpi433rc/app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import logging

from flask import Flask

from .config import DEBUG

level = logging.DEBUG if DEBUG else logging.INFO
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=level)

app = Flask(__name__)

from .api.flaskutil.routing import OnOffConverter
Expand Down
4 changes: 3 additions & 1 deletion rpi433rc/business/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from schema import Schema, Or, Use

from .util import LogMixin


class UnknownDeviceError(Exception):
pass
Expand Down Expand Up @@ -95,7 +97,7 @@ class StatefulDevice(object):


@attr.s
class DeviceStore(object):
class DeviceStore(LogMixin):
"""
Abstract base classes for storing / fetching configured devices.
"""
Expand Down
10 changes: 8 additions & 2 deletions rpi433rc/business/rc433.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import attr

from .devices import CodeDevice
from .devices import CodeDevice, StatefulDevice
from .util import LogMixin

try:
from rpi_rf import RFDevice
Expand All @@ -26,7 +27,7 @@ class UnsupportedDeviceError(Exception):


@attr.s
class RC433(object):
class RC433(LogMixin):
"""
Remote control 433mhz devices.
"""
Expand Down Expand Up @@ -59,6 +60,7 @@ def send_code(self, code):
raise TypeError("Argument code is expected to be an int, but given is '{}'".format(type(code)))

self._initialize()
self.logger.debug("Sending code '{}'".format(code))
return any([self.rf_device.tx_code(code) for _ in range(5)])

def switch_device(self, device, on):
Expand All @@ -74,6 +76,10 @@ def switch_device(self, device, on):
Returns:
Returns True if the underlying RFDevice acknowledged; otherwise False.
"""
self.logger.debug("Device switch for '{}' to '{}' requested".format(str(device), str(on)))
if isinstance(device, StatefulDevice):
# Unpack the actual device from the Stateful device wrapper
device = device.device
if isinstance(device, CodeDevice):
return self.send_code(device.code_on if on else device.code_off)

Expand Down
16 changes: 16 additions & 0 deletions rpi433rc/business/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import logging


class LogMixin(object):
"""
Example:
>>> class Dummy(LogMixin):
... def do_something_fancy(self):
... self.logger.info("Begin")
... # ...
... self.logger.info("End")
"""
@property
def logger(self):
return logging.getLogger(self.__class__.__name__)
6 changes: 5 additions & 1 deletion rpi433rc/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
GPIO_OUT = 17
import os

GPIO_OUT = int(os.environ.get('GPIO_OUT', 17))
CONFIG_DIR = os.environ.get('CONFIG_DIR', os.path.join(os.path.dirname(__file__), '../conf'))
DEBUG = bool(os.environ.get('DEBUG', False))

VERSION = '1.0.0'
5 changes: 5 additions & 0 deletions run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash

export FLASK_APP=`pwd`/rpi433rc/app.py

flask run --host=0.0.0.0
32 changes: 32 additions & 0 deletions tests/flask/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import pytest

from rpi433rc.app import app
from rpi433rc.business.devices import StatefulDevice, CodeDevice, SystemDevice
from rpi433rc.business.rc433 import RFDevice
from rpi433rc.api import device_db


@pytest.yield_fixture(scope='session')
Expand All @@ -13,3 +16,32 @@ def flask_app():
def flask_client(flask_app):
# flask_app.response_class = utils.JSONResponse
return flask_app.test_client()


@pytest.yield_fixture(scope='function')
def mocked_rfdevice(mocker):
mocker.patch.object(RFDevice, 'enable_tx')
mocker.patch.object(RFDevice, 'tx_code')
mocker.patch.object(RFDevice, 'cleanup')
RFDevice.enable_tx.return_value = None
RFDevice.tx_code.return_value = True
RFDevice.cleanup.return_value = None

yield RFDevice


@pytest.yield_fixture(scope='function')
def mocked_device_db(mocker):
mocker.patch.object(device_db, 'list')
mocker.patch.object(device_db, 'lookup')
mocker.patch.object(device_db, 'switch')

device_db.list.return_value = [
StatefulDevice(device=CodeDevice('device1', code_on=12345, code_off=23456), state=False),
StatefulDevice(device=CodeDevice('device2', code_on=12345, code_off=23456), state=True),
StatefulDevice(device=SystemDevice('device3', system_code="00001", device_code=4), state=True),
]
device_db.lookup.return_value = StatefulDevice(device=CodeDevice('device1', code_on=12345, code_off=23456), state=False)
device_db.switch.return_value = None

yield device_db
Loading

0 comments on commit d402df6

Please sign in to comment.