Skip to content

Commit

Permalink
Merge remote-tracking branch 'kipe/master'
Browse files Browse the repository at this point in the history
* kipe/master: (156 commits)
  Bump version to v0.40. (kipe#60)
  [add] a5-04-01 Temperature and Humidity Sensor (kipe#59)
  [fix] a5-10-12 scale was inversed (kipe#58)
  [add] a5-38-08 Gatway dimming command (kipe#56)
  Support command for any rorg (kipe#55)
  [add] a5-10-12 Temperature and Humidity Sensor and Set Point (kipe#54)
  Added Equipment Profile (EEP), A5-14-01 (4BS) (kipe#48)
  [add] EEP A5-30-03 (kipe#46)
  [add] EEP A5-08-01 Light, Temperature and Occupancy Sensor (kipe#45)
  [fix] correct scale for a5-10-03 room operating panel (kipe#43)
  Add .venv to .gitignore.
  Update SUPPORTED_PROFILES.md. Closes issue kipe#40.
  [add] EEP.xml: Mechanical Window handle (F6-10-00) (kipe#41)
  broken link (kipe#39)
  [add] EEP.xml: F6-05-01 (kipe#38)
  Minor fixes.
  Update EEP.xml
  update logger message
  Use floats for value scale and range in generate_supported_profiles.py
  Update SUPPORTED_PROFILES.md
  ...
  • Loading branch information
Douwe van der Meij committed Oct 26, 2017
2 parents dd7e4fa + 1d8d410 commit 25eb3a7
Show file tree
Hide file tree
Showing 38 changed files with 3,394 additions and 425 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ include
lib
local
share
lib-python
lib_pypy
site-packages
venv
*.pyc

*.egg-info
build
man
MANIFEST
dist
pip-selfcheck.json
16 changes: 13 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
language: python
python:
- "2.6"
- "2.7"
- "3.2"
- "3.3"
- "3.4"
- "3.5"
- "nightly"
- "pypy"
- "pypy3"
install:
- pip install pylint coverage coveralls
- pip install -e .
script:
- nosetests
- nosetests -s -q --with-coverage --cover-package=enocean
sudo: false
matrix:
fast_finish: true

after_success:
- pylint enocean -d line-too-long
- coveralls
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2014-2016 Kimmo Huoman

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Python EnOcean #

[![Build Status](https://travis-ci.org/kipe/enocean.svg?branch=master)](https://travis-ci.org/kipe/enocean)
[![Coverage Status](https://coveralls.io/repos/github/kipe/enocean/badge.svg?branch=master)](https://coveralls.io/github/kipe/enocean?branch=master)

A Python library for reading and controlling [EnOcean](http://www.enocean.com/) devices.
Part of [Forget Me Not](http://www.element14.com/community/community/design-challenges/forget-me-not)
design challenge @ [element14](http://www.element14.com/).

**Still a work-in-progress, so API might (and most probably will) change, as I move onto really using the library myself.**
Started as a part of [Forget Me Not](http://www.element14.com/community/community/design-challenges/forget-me-not)
design challenge @ [element14](http://www.element14.com/).

## Install on Raspberry Pi ##
## Install ##

If not installed already, install [pip](https://pypi.python.org/pypi/pip) by running

Expand All @@ -22,6 +22,6 @@ After this, it's just a matter of running `enocean_example.py` and pressing the
learn button on magnetic contact or temperature switch or pressing the rocker switch.

You should be displayed with a log of the presses, as well as parsed values
(assuming the sensors are the ones provided in the [EnOcean Sensor Kit](http://uk.farnell.com/raspberypi-enocean-sensor-kit)).
(assuming the sensors are the ones provided in the [EnOcean Starter Kit](https://www.enocean.com/en/enocean_modules/esk-300)).

The example script can be stopped by pressing `CTRL+C`
436 changes: 436 additions & 0 deletions SUPPORTED_PROFILES.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions enocean/communicators/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
''' Provider for different Communicator -classes for EnOcean. '''
from enocean.communicators.communicator import Communicator
from enocean.communicators.serialcommunicator import SerialCommunicator
from enocean.communicators.tcpcommunicator import TCPCommunicator
74 changes: 59 additions & 15 deletions enocean/communicators/communicator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- encoding: utf-8 -*-
from __future__ import print_function, unicode_literals, division
from __future__ import print_function, unicode_literals, division, absolute_import
import logging

import threading
Expand All @@ -8,17 +8,17 @@
except ImportError:
import Queue as queue
from enocean.protocol.packet import Packet
from enocean.protocol.constants import PARSE_RESULT

logger = logging.getLogger('enocean.communicators.Communicator')
from enocean.protocol.constants import PACKET, PARSE_RESULT, RETURN_CODE


class Communicator(threading.Thread):
'''
Communicator base-class for EnOcean.
Not to be used directly, only serves as base class for SerialCommunicator etc.
'''
def __init__(self):
logger = logging.getLogger('enocean.communicators.Communicator')

def __init__(self, callback=None, teach_in=True):
super(Communicator, self).__init__()
# Create an event to stop the thread
self._stop_flag = threading.Event()
Expand All @@ -27,21 +27,28 @@ def __init__(self):
# Setup packet queues
self.transmit = queue.Queue()
self.receive = queue.Queue()
# Set the callback method
self.__callback = callback
# Internal variable for the Base ID of the module.
self._base_id = None
# Should new messages be learned automatically? Defaults to True.
# TODO: Not sure if we should use CO_WR_LEARNMODE??
self.teach_in = teach_in

def _get_from_send_queue(self):
''' Get message from send queue, if one exists '''
try:
p = self.transmit.get(block=False)
logger.info('Sending packet')
logger.debug(p)
return p
packet = self.transmit.get(block=False)
self.logger.info('Sending packet')
self.logger.debug(packet)
return packet
except queue.Empty:
pass
return None

def send(self, packet):
if not isinstance(packet, Packet):
logger.error('Object to send must be an instance of Packet')
self.logger.error('Object to send must be an instance of Packet')
return False
self.transmit.put(packet)
return True
Expand All @@ -53,12 +60,49 @@ def parse(self):
''' Parses messages and puts them to receive queue '''
# Loop while we get new messages
while True:
status, self._buffer, p = Packet.parse_msg(self._buffer)
status, self._buffer, packet = Packet.parse_msg(self._buffer, communicator=self)
# If message is incomplete -> break the loop
if status == PARSE_RESULT.INCOMPLETE:
return status

# If message is OK, add it to receive queue
if status == PARSE_RESULT.OK and p:
self.receive.put(p)
logger.debug(p)
# If message is OK, add it to receive queue or send to the callback method
if status == PARSE_RESULT.OK and packet:
if self.__callback is None:
self.receive.put(packet)
else:
self.__callback(packet)
self.logger.debug(packet)

@property
def base_id(self):
''' Fetches Base ID from the transmitter, if required. Otherwise returns the currently set Base ID. '''
# If base id is already set, return it.
if self._base_id is not None:
return self._base_id

# Send COMMON_COMMAND 0x08, CO_RD_IDBASE request to the module
self.send(Packet(PACKET.COMMON_COMMAND, data=[0x08]))
# Loop over 10 times, to make sure we catch the response.
# Thanks to timeout, shouldn't take more than a second.
# Unfortunately, all other messages received during this time are ignored.
for i in range(0, 10):
try:
packet = self.receive.get(block=True, timeout=0.1)
# We're only interested in responses to the request in question.
if packet.packet_type == PACKET.RESPONSE and packet.response == RETURN_CODE.OK and len(packet.response_data) == 4:
# Base ID is set in the response data.
self._base_id = packet.response_data
# Put packet back to the Queue, so the user can also react to it if required...
self.receive.put(packet)
break
# Put other packets back to the Queue.
self.receive.put(packet)
except queue.Empty:
continue
# Return the current Base ID (might be None).
return self._base_id

@base_id.setter
def base_id(self, base_id):
''' Sets the Base ID manually, only for testing purposes. '''
self._base_id = base_id
22 changes: 11 additions & 11 deletions enocean/communicators/serialcommunicator.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
# -*- encoding: utf-8 -*-
from __future__ import print_function, unicode_literals, division
from __future__ import print_function, unicode_literals, division, absolute_import
import logging
import serial

from enocean.communicators.communicator import Communicator

logger = logging.getLogger('enocean.communicators.SerialCommunicator')


class SerialCommunicator(Communicator):
''' Serial port communicator class for EnOcean radio '''
def __init__(self, port='/dev/ttyAMA0'):
super(SerialCommunicator, self).__init__()
logger = logging.getLogger('enocean.communicators.SerialCommunicator')

def __init__(self, port='/dev/ttyAMA0', callback=None):
super(SerialCommunicator, self).__init__(callback)
# Initialize serial port
self.__ser = serial.Serial(port, 57600, timeout=0.1)

def run(self):
logger.info('SerialCommunicator started')
self.logger.info('SerialCommunicator started')
while not self._stop_flag.is_set():
# If there's messages in transmit queue
# send them
while True:
p = self._get_from_send_queue()
if not p:
packet = self._get_from_send_queue()
if not packet:
break
self.__ser.write(bytearray(p.build()))
self.__ser.write(bytearray(packet.build()))

# Read chars from serial port as hex numbers
try:
self._buffer.extend(bytearray(self.__ser.read(16)))
except serial.SerialException:
logger.error('Serial port exception! (device disconnected or multiple access on port?)')
self.logger.error('Serial port exception! (device disconnected or multiple access on port?)')
break
self.parse()

self.__ser.close()
logger.info('SerialCommunicator stopped')
self.logger.info('SerialCommunicator stopped')
20 changes: 10 additions & 10 deletions enocean/communicators/tcpcommunicator.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# -*- encoding: utf-8 -*-
from __future__ import print_function, unicode_literals, division
from __future__ import print_function, unicode_literals, division, absolute_import
import logging
import socket

from enocean.communicators.communicator import Communicator

logger = logging.getLogger('enocean.communicators.TCPCommunicator')


class TCPCommunicator(Communicator):
''' Socket communicator class for EnOcean radio '''
logger = logging.getLogger('enocean.communicators.TCPCommunicator')

def __init__(self, host='', port=9637):
super(TCPCommunicator, self).__init__()
self.host = host
self.port = port

def run(self):
logger.info('TCPCommunicator started')
self.logger.info('TCPCommunicator started')
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((self.host, self.port))
sock.listen(5)
Expand All @@ -27,18 +27,18 @@ def run(self):
(client, addr) = sock.accept()
except socket.timeout:
continue
logger.debug('Client connected')
self.logger.debug('Client "%s" connected' % (addr))
client.settimeout(0.5)
while True and not self._stop_flag.is_set():
try:
d = client.recv(2048)
data = client.recv(2048)
except socket.timeout:
break
if not d:
if not data:
break
self._buffer.extend(bytearray(d))
self._buffer.extend(bytearray(data))
self.parse()
client.close()
logger.debug('Client disconnected')
self.logger.debug('Client disconnected')
sock.close()
logger.info('TCPCommunicator stopped')
self.logger.info('TCPCommunicator stopped')
Loading

0 comments on commit 25eb3a7

Please sign in to comment.