diff options
| author | manuel <manuel@mausz.at> | 2013-12-25 18:31:56 +0100 |
|---|---|---|
| committer | manuel <manuel@mausz.at> | 2013-12-25 18:31:56 +0100 |
| commit | d70072c2f4c348ba2f6a562fa31e3245209cf21f (patch) | |
| tree | dd77a2ad352d8965965833a86922e26e67693e80 | |
| parent | 4306c0ed8d95bbaca875f4f1d9badb436cb4e2b9 (diff) | |
| download | webiopi-d70072c2f4c348ba2f6a562fa31e3245209cf21f.tar.gz webiopi-d70072c2f4c348ba2f6a562fa31e3245209cf21f.tar.bz2 webiopi-d70072c2f4c348ba2f6a562fa31e3245209cf21f.zip | |
add support for ICMP
| -rw-r--r-- | htdocs/webiopi.js | 37 | ||||
| -rw-r--r-- | python/webiopi/devices/sensor/__init__.py | 13 | ||||
| -rw-r--r-- | python/webiopi/devices/sensor/icmp.py | 131 |
3 files changed, 181 insertions, 0 deletions
diff --git a/htdocs/webiopi.js b/htdocs/webiopi.js index c7eae66..04029d7 100644 --- a/htdocs/webiopi.js +++ b/htdocs/webiopi.js | |||
| @@ -665,6 +665,10 @@ WebIOPi.prototype.newDevice = function(type, name) { | |||
| 665 | if (type == "Distance") { | 665 | if (type == "Distance") { |
| 666 | return new Distance(name); | 666 | return new Distance(name); |
| 667 | } | 667 | } |
| 668 | |||
| 669 | if (type == "Duration") { | ||
| 670 | return new Duration(name); | ||
| 671 | } | ||
| 668 | 672 | ||
| 669 | if (type == "PiFaceDigital") { | 673 | if (type == "PiFaceDigital") { |
| 670 | return new PiFaceDigital(name); | 674 | return new PiFaceDigital(name); |
| @@ -1347,6 +1351,39 @@ Distance.prototype.refreshUI = function() { | |||
| 1347 | }); | 1351 | }); |
| 1348 | } | 1352 | } |
| 1349 | 1353 | ||
| 1354 | function Duration(name) { | ||
| 1355 | this.name = name; | ||
| 1356 | this.url = "/devices/" + name + "/sensor"; | ||
| 1357 | this.refreshTime = 1000; | ||
| 1358 | } | ||
| 1359 | |||
| 1360 | Duration.prototype.toString = function() { | ||
| 1361 | return this.name + ": Duration"; | ||
| 1362 | } | ||
| 1363 | |||
| 1364 | Duration.prototype.getMillimeter = function(callback) { | ||
| 1365 | $.get(this.url + "/duration/ms", function(data) { | ||
| 1366 | callback(this.name, data); | ||
| 1367 | }); | ||
| 1368 | } | ||
| 1369 | |||
| 1370 | Duration.prototype.refreshUI = function() { | ||
| 1371 | var dist = this; | ||
| 1372 | var element = this.element; | ||
| 1373 | |||
| 1374 | if ((element != undefined) && (element.header == undefined)) { | ||
| 1375 | element.header = $("<h3>" + this + "</h3>"); | ||
| 1376 | element.append(element.header); | ||
| 1377 | } | ||
| 1378 | |||
| 1379 | this.getMillimeter(function(name, data){ | ||
| 1380 | if (element != undefined) { | ||
| 1381 | element.header.text(dist + ": " + data + "ms"); | ||
| 1382 | } | ||
| 1383 | setTimeout(function(){dist.refreshUI()}, dist.refreshTime); | ||
| 1384 | }); | ||
| 1385 | } | ||
| 1386 | |||
| 1350 | function PiFaceDigital(name) { | 1387 | function PiFaceDigital(name) { |
| 1351 | this.name = name; | 1388 | this.name = name; |
| 1352 | this.url = "/devices/" + name + "/digital"; | 1389 | this.url = "/devices/" + name + "/digital"; |
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(): | |||
| 169 | def getYard(self): | 169 | def getYard(self): |
| 170 | return self.getInch() / 36 | 170 | return self.getInch() / 36 |
| 171 | 171 | ||
| 172 | class Duration(): | ||
| 173 | def __family__(self): | ||
| 174 | return "Duration" | ||
| 175 | |||
| 176 | def __getMilliseconds__(self): | ||
| 177 | raise NotImplementedError | ||
| 178 | |||
| 179 | @request("GET", "sensor/duration/ms") | ||
| 180 | @response("%.02f") | ||
| 181 | def getMilliseconds(self): | ||
| 182 | return self.__getMilliseconds__() | ||
| 183 | |||
| 172 | DRIVERS = {} | 184 | DRIVERS = {} |
| 173 | DRIVERS["bmp085"] = ["BMP085"] | 185 | DRIVERS["bmp085"] = ["BMP085"] |
| 174 | DRIVERS["onewiretemp"] = ["DS1822", "DS1825", "DS18B20", "DS18S20", "DS28EA00"] | 186 | DRIVERS["onewiretemp"] = ["DS1822", "DS1825", "DS18B20", "DS18S20", "DS28EA00"] |
| 175 | DRIVERS["tmpXXX"] = ["TMP75", "TMP102", "TMP275"] | 187 | DRIVERS["tmpXXX"] = ["TMP75", "TMP102", "TMP275"] |
| 176 | DRIVERS["tslXXXX"] = ["TSL2561", "TSL2561CS", "TSL2561T", "TSL4531", "TSL45311", "TSL45313", "TSL45315", "TSL45317"] | 188 | DRIVERS["tslXXXX"] = ["TSL2561", "TSL2561CS", "TSL2561T", "TSL4531", "TSL45311", "TSL45313", "TSL45315", "TSL45317"] |
| 177 | DRIVERS["vcnl4000"] = ["VCNL4000"] | 189 | DRIVERS["vcnl4000"] = ["VCNL4000"] |
| 190 | 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 @@ | |||
| 1 | # Copyright 2013 Manuel Mausz | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.devices.sensor import Duration | ||
| 16 | from webiopi.utils.logger import debug | ||
| 17 | |||
| 18 | import socket, struct, select, random | ||
| 19 | from time import time | ||
| 20 | |||
| 21 | ICMP_ECHO_REQUEST = 8 | ||
| 22 | ICMP_CODE = socket.getprotobyname('icmp') | ||
| 23 | ERROR_DESCR = { | ||
| 24 | 1: 'ERROR: ICMP messages can only be sent from processes running as root.', | ||
| 25 | 10013: 'ERROR: ICMP messages can only be sent by users or processes with administrator rights.' | ||
| 26 | } | ||
| 27 | |||
| 28 | class ICMP(Duration): | ||
| 29 | def __init__(self, ip='127.0.0.1'): | ||
| 30 | self.ip = ip | ||
| 31 | |||
| 32 | def __str__(self): | ||
| 33 | return "ICMP(ip=%s)" % self.ip | ||
| 34 | |||
| 35 | def __family__(self): | ||
| 36 | return Duration.__family__(self) | ||
| 37 | |||
| 38 | def __getMilliseconds__(self): | ||
| 39 | return self.echo(self.ip, 1) * 1000 | ||
| 40 | |||
| 41 | def close(self): | ||
| 42 | pass | ||
| 43 | |||
| 44 | def __checksum__(self, source_string): | ||
| 45 | sum = 0 | ||
| 46 | count_to = (len(source_string) / 2) * 2 | ||
| 47 | count = 0 | ||
| 48 | while count < count_to: | ||
| 49 | this_val = ord(source_string[count + 1])*256+ord(source_string[count]) | ||
| 50 | sum = sum + this_val | ||
| 51 | sum = sum & 0xffffffff # Necessary? | ||
| 52 | count = count + 2 | ||
| 53 | if count_to < len(source_string): | ||
| 54 | sum = sum + ord(source_string[len(source_string) - 1]) | ||
| 55 | sum = sum & 0xffffffff # Necessary? | ||
| 56 | sum = (sum >> 16) + (sum & 0xffff) | ||
| 57 | sum = sum + (sum >> 16) | ||
| 58 | answer = ~sum | ||
| 59 | answer = answer & 0xffff | ||
| 60 | |||
| 61 | answer = answer >> 8 | (answer << 8 & 0xff00) | ||
| 62 | return answer | ||
| 63 | |||
| 64 | |||
| 65 | def __create_packet__(self, id): | ||
| 66 | """Creates a new echo request packet based on the given "id".""" | ||
| 67 | # Builds Dummy Header | ||
| 68 | # Header is type (8), code (8), checksum (16), id (16), sequence (16) | ||
| 69 | header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, id, 1) | ||
| 70 | data = 192 * 'Q' | ||
| 71 | |||
| 72 | # Builds Real Header | ||
| 73 | header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, socket.htons(self.__checksum__(header + data)), id, 1) | ||
| 74 | return header + data | ||
| 75 | |||
| 76 | |||
| 77 | def __response_handler__(self, sock, packet_id, time_sent, timeout): | ||
| 78 | """Handles packet response, returning either the delay or timing out (returns "None").""" | ||
| 79 | while True: | ||
| 80 | ready = select.select([sock], [], [], timeout) | ||
| 81 | if ready[0] == []: # Timeout | ||
| 82 | return | ||
| 83 | |||
| 84 | time_received = time() | ||
| 85 | rec_packet, addr = sock.recvfrom(1024) | ||
| 86 | icmp_header = rec_packet[20:28] | ||
| 87 | type, code, checksum, rec_id, sequence = struct.unpack('bbHHh', icmp_header) | ||
| 88 | |||
| 89 | if rec_id == packet_id: | ||
| 90 | return time_received - time_sent | ||
| 91 | |||
| 92 | timeout -= time_received - time_sent | ||
| 93 | if timeout <= 0: | ||
| 94 | return | ||
| 95 | |||
| 96 | def echo(self, dest_addr, timeout=1): | ||
| 97 | """ | ||
| 98 | Sends one ICMP packet to the given destination address (dest_addr) | ||
| 99 | which can be either an ip or a hostname. | ||
| 100 | |||
| 101 | "timeout" can be any integer or float except for negatives and zero. | ||
| 102 | |||
| 103 | Returns either the delay (in seconds), or "None" on timeout or an | ||
| 104 | invalid address, respectively. | ||
| 105 | |||
| 106 | """ | ||
| 107 | |||
| 108 | try: | ||
| 109 | sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE) | ||
| 110 | except socket.error, (error_number, msg): | ||
| 111 | if error_number in ERROR_DESCR: | ||
| 112 | # Operation not permitted | ||
| 113 | raise socket.error(''.join((msg, ERROR_DESCR[error_number]))) | ||
| 114 | raise # Raises the original error | ||
| 115 | |||
| 116 | try: | ||
| 117 | socket.gethostbyname(dest_addr) | ||
| 118 | except socket.gaierror: | ||
| 119 | return | ||
| 120 | |||
| 121 | packet_id = int((id(timeout) * random.random()) % 65535) | ||
| 122 | packet = self.__create_packet__(packet_id) | ||
| 123 | while packet: | ||
| 124 | # The icmp protocol does not use a port, but the function | ||
| 125 | # below expects it, so we just give it a dummy port. | ||
| 126 | sent = sock.sendto(packet, (dest_addr, 1)) | ||
| 127 | packet = packet[sent:] | ||
| 128 | |||
| 129 | delay = self.__response_handler__(sock, packet_id, time(), timeout) | ||
| 130 | sock.close() | ||
| 131 | return delay | ||
