From d70072c2f4c348ba2f6a562fa31e3245209cf21f Mon Sep 17 00:00:00 2001 From: manuel Date: Wed, 25 Dec 2013 18:31:56 +0100 Subject: add support for ICMP --- python/webiopi/devices/sensor/__init__.py | 13 +++ python/webiopi/devices/sensor/icmp.py | 131 ++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 python/webiopi/devices/sensor/icmp.py (limited to 'python') diff --git a/python/webiopi/devices/sensor/__init__.py b/python/webiopi/devices/sensor/__init__.py index 598a82f..f7879ec 100644 --- a/python/webiopi/devices/sensor/__init__.py +++ b/python/webiopi/devices/sensor/__init__.py @@ -169,9 +169,22 @@ class Distance(): def getYard(self): return self.getInch() / 36 +class Duration(): + def __family__(self): + return "Duration" + + def __getMilliseconds__(self): + raise NotImplementedError + + @request("GET", "sensor/duration/ms") + @response("%.02f") + def getMilliseconds(self): + return self.__getMilliseconds__() + DRIVERS = {} DRIVERS["bmp085"] = ["BMP085"] DRIVERS["onewiretemp"] = ["DS1822", "DS1825", "DS18B20", "DS18S20", "DS28EA00"] DRIVERS["tmpXXX"] = ["TMP75", "TMP102", "TMP275"] DRIVERS["tslXXXX"] = ["TSL2561", "TSL2561CS", "TSL2561T", "TSL4531", "TSL45311", "TSL45313", "TSL45315", "TSL45317"] DRIVERS["vcnl4000"] = ["VCNL4000"] +DRIVERS["icmp"] = ["ICMP"] diff --git a/python/webiopi/devices/sensor/icmp.py b/python/webiopi/devices/sensor/icmp.py new file mode 100644 index 0000000..8a4065d --- /dev/null +++ b/python/webiopi/devices/sensor/icmp.py @@ -0,0 +1,131 @@ +# Copyright 2013 Manuel Mausz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from webiopi.devices.sensor import Duration +from webiopi.utils.logger import debug + +import socket, struct, select, random +from time import time + +ICMP_ECHO_REQUEST = 8 +ICMP_CODE = socket.getprotobyname('icmp') +ERROR_DESCR = { + 1: 'ERROR: ICMP messages can only be sent from processes running as root.', + 10013: 'ERROR: ICMP messages can only be sent by users or processes with administrator rights.' +} + +class ICMP(Duration): + def __init__(self, ip='127.0.0.1'): + self.ip = ip + + def __str__(self): + return "ICMP(ip=%s)" % self.ip + + def __family__(self): + return Duration.__family__(self) + + def __getMilliseconds__(self): + return self.echo(self.ip, 1) * 1000 + + def close(self): + pass + + def __checksum__(self, source_string): + sum = 0 + count_to = (len(source_string) / 2) * 2 + count = 0 + while count < count_to: + this_val = ord(source_string[count + 1])*256+ord(source_string[count]) + sum = sum + this_val + sum = sum & 0xffffffff # Necessary? + count = count + 2 + if count_to < len(source_string): + sum = sum + ord(source_string[len(source_string) - 1]) + sum = sum & 0xffffffff # Necessary? + sum = (sum >> 16) + (sum & 0xffff) + sum = sum + (sum >> 16) + answer = ~sum + answer = answer & 0xffff + + answer = answer >> 8 | (answer << 8 & 0xff00) + return answer + + + def __create_packet__(self, id): + """Creates a new echo request packet based on the given "id".""" + # Builds Dummy Header + # Header is type (8), code (8), checksum (16), id (16), sequence (16) + header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, id, 1) + data = 192 * 'Q' + + # Builds Real Header + header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, socket.htons(self.__checksum__(header + data)), id, 1) + return header + data + + + def __response_handler__(self, sock, packet_id, time_sent, timeout): + """Handles packet response, returning either the delay or timing out (returns "None").""" + while True: + ready = select.select([sock], [], [], timeout) + if ready[0] == []: # Timeout + return + + time_received = time() + rec_packet, addr = sock.recvfrom(1024) + icmp_header = rec_packet[20:28] + type, code, checksum, rec_id, sequence = struct.unpack('bbHHh', icmp_header) + + if rec_id == packet_id: + return time_received - time_sent + + timeout -= time_received - time_sent + if timeout <= 0: + return + + def echo(self, dest_addr, timeout=1): + """ + Sends one ICMP packet to the given destination address (dest_addr) + which can be either an ip or a hostname. + + "timeout" can be any integer or float except for negatives and zero. + + Returns either the delay (in seconds), or "None" on timeout or an + invalid address, respectively. + + """ + + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE) + except socket.error, (error_number, msg): + if error_number in ERROR_DESCR: + # Operation not permitted + raise socket.error(''.join((msg, ERROR_DESCR[error_number]))) + raise # Raises the original error + + try: + socket.gethostbyname(dest_addr) + except socket.gaierror: + return + + packet_id = int((id(timeout) * random.random()) % 65535) + packet = self.__create_packet__(packet_id) + while packet: + # The icmp protocol does not use a port, but the function + # below expects it, so we just give it a dummy port. + sent = sock.sendto(packet, (dest_addr, 1)) + packet = packet[sent:] + + delay = self.__response_handler__(sock, packet_id, time(), timeout) + sock.close() + return delay -- cgit v1.2.3