eapi
 Alle Klassen Funktionen
net.py
1 # -*- coding: utf-8 -*-
2 
3 """Ein Modul, das die Eingabe-Ausgabe-Module für den Raspberry Pi
4 netzwerkfähig macht.
5 
6 Der Server kann mit folgenden Zeilen einfach erstellt werden:
7 
8 >>> from eapi.net import EAModulServer
9 >>> easerver = EAModulServer("localhost", 9999)
10 
11 Anschließend wird er mit einem Aufruf gestartet.
12 
13  easerver.serve_forever()
14 
15 Ebenso kann der Server über die Kommandozeile mit dem folgenden Befehl
16 gestartet werden.
17 
18  $ python3 -m eapi.net startserver
19 
20 Nun wartet der Server auf dem Port 9999 auf UDP-Pakete. Ein an den Server
21 gesendeter Request besteht aus genau einem Byte - weitere gesendete Bytes
22 werden ignoriert. Die letzen sechs Bit (0 oder 1) des gesendeten Bytes, werden
23 als Werte für die rote, gelbe und grüne LED interpretiert:
24 
25  ? ? 0 0 1 1 1 0
26  ^ ^ ^
27  | | |
28  | | grün
29  | gelb
30  rot
31 
32 Für jede Farbe werden zwei Bit verwendet. Das erste Bit besagt, ob die
33 entsprechende LED geschaltet werden soll (1) oder nicht (0). Wenn die LED
34 geschalet werden soll, beschreibt das zweite Bit den Zustand, in den die LED
35 geschaltet werden soll: an (1) oder aus (0). Wenn die LED nicht geschaltet
36 werden soll, ist der Wert beliebig.
37 
38 Die Bitsequenz ?? 0? 11 10 (? bedeutet 'beliebig') belässt die rote LED in
39 ihrem bisherigen Zustand, schaltet die gelbe LED an und die grüne LED aus.
40 
41 Mit Netcat und echo kann ein Byte einfach an einen Testserver wie folgt
42 gesendet werden:
43 
44  $ echo -en '\\xE' | nc -4u localhost 9999
45 
46 Der Hexwert E entspricht dem Binärwert 00001110. Mit der Option -e wird eine
47 Escapesequenz verschickt, die Option -n besagt, dass kein Zeilenumbruch
48 gesendet werden soll - also nur die angegebenen Bytes. Die Option -4 von nc
49 sendet ein IPv4-Paket, das als UDP-Paket (-u) verschickt werden soll.
50 
51 Das Modul enthält einen einfachen Konsolenclient, der über die Konsole
52 gestartet werden kann:
53 
54  $ python3 -m eapi.net startclient
55 
56 Mit dem Python-Modul socket kann ein Packet in Python selbst erstellt und an
57 den Server gesendet werden.
58 
59 >>> import socket
60 >>> client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
61 >>> daten = bytes([0xe])
62 
63 Die zu sendenen Daten bestehen aus nur einem Byte.
64 
65 >>> client.sendto(daten, ("localhost", 9999))
66 1
67 
68 
69 """
70 
71 # TODO Modul sendet an MQTT-Broker: https://www.dinotools.de/2015/04/12/mqtt-mit-python-nutzen/
72 
73 import socket
74 import socketserver
75 from eapi.hw import EAModul
76 
77 
78 class EAModulUDPHandler(socketserver.BaseRequestHandler):
79  """Ein Handler für UDP requests an den EAModulServer."""
80 
81  # eamodul als Klassenattribut, da für jeden Request auf den Server
82  # eine neue Handlerinstanz erzeugt wird.
83  eamodul = None
84 
85  def handle(self):
86  """Der UDP-Handler bearbeitet UDP-Requests gemäß der Modulbeschreibung
87  (s.o.)."""
88  print("request erhalten!", self.request)
89 
90  # statisches Modul initaisieren, falls noch nicht geschehen
91  if EAModulUDPHandler.eamodul is None:
92  EAModulUDPHandler.eamodul = EAModul()
93 
94  # Der Request besteht aus einem Tupel aus Daten und Socket des
95  # Senders. Wir greifen die Daten heraus.
96  data = self.request[0]
97 
98  # Erwarte mindestens ein Byte im Request
99  if len(data) < 1:
100  return
101 
102  # ?? ?? ??
103  # ro ge gr
104  # 31 84 21
105  # 26
106  #
107  byte = int(data[0])
108  if byte & 32 == 32:
109  EAModulUDPHandler.eamodul.schalte_led(EAModul.LED_ROT,
110  byte & 16 == 16)
111  if byte & 8 == 8:
112  EAModulUDPHandler.eamodul.schalte_led(EAModul.LED_GELB,
113  byte & 4 == 4)
114  if byte & 2 == 2:
115  EAModulUDPHandler.eamodul.schalte_led(EAModul.LED_GRUEN,
116  byte & 1 == 1)
117 
118 
119 class EAModulServer(socketserver.UDPServer):
120  """Ein UDPServer für ein EA-Modul.
121 
122  Er lässt sich unter Angabe von hostname und Portnummer erzeugen.
123 
124  >>> easerver = EAModulServer("localhost", 9999)
125 
126  Anschließend kann er mit dem folgenden Aufruf gestartet werden
127 
128  easerver.serve_forever()
129 
130  Ein an den Server gesendeter Request wird vom EAModulUDPHandler
131  verarbeitet.
132  """
133 
134  def __init__(self, host, port, eamodul=None):
135  """Starte einen Server auf dem angegebnen hostname, oder IP-Adresse -
136  lokale Server können hier auch 'localhost' als Name verwenden.
137 
138  Über den Parameter eamodul kann ein EAModul übergeben werden. Wird
139  kein Modul übergeben, wird ein Standardmodul selbst erstellt.
140  """
141 
142  super().__init__((host, port), EAModulUDPHandler)
143 
144  if eamodul:
145  EAModulUDPHandler.eamodul = eamodul
146 
147 
149  """Client, um auf den EAModulServer zuzugreifen.
150 
151  Der Client kann mit der Angabe eines Hostnamens oder einer IP-Adresse
152  gestartet werden.
153 
154  >>> from eapi.net import EAModulClient
155  >>> client = EAModulClient('localhost', 9999)
156 
157  Nun kann er mit dem Server kommunizieren und die dortigen LEDs ansteuern.
158 
159  >>> client.sende(rot=1, gelb=0, gruen=1)
160  >>> client.sende(rot=0, gelb=0, gruen=1)
161 
162  Die Methoden lassen sich auch kürzer aufrufen.
163 
164  >>> client.sende(1, 0, 1)
165  >>> client.sende(0, 0, 1)
166 
167  Wenn ein Wert ungleich 0 oder 1 gesendet wird, so wird er ignoriert und die
168  LED behält ihren Wert bei.
169 
170  >>> client.sende(1, 9, 1)
171 
172  schaltet die rote und grüne LED ein und belässt die gelbe LED in ihrem
173  bisherigen Zustand.
174  """
175 
176  def __init__(self, servername, serverport):
177  """Starte den Client für einen laufenden Server.
178 
179  Der angegebene servername ist eine IP-Adresse oder ein Domainname -
180  für ein lokal laufenden Server kann auch localhost verwendet
181  werden. Mit serverport wird die Portnummer angegeben, über die der
182  Server ansprechbar ist.
183  """
184  self.servername = servername
185  self.serverport = serverport
186  self.client = socket.socket(socket.AF_INET, # Address Family Internet
187  socket.SOCK_DGRAM) # UDP
188 
189  def sende(self, rot, gelb, gruen):
190  """Sende an den Server die Information, welche LEDs an- bzw.
191  ausgeschaltet werden sollen.
192 
193  Werte von 0 oder 1 für rot, gelb und grün schalten die LED aus bzw. an.
194  Andere Werte werden ignoriert und belassen die LED in ihrem bisherigen
195  Zustand.
196  """
197 
198  # ? ? ? ? ? ?
199  # r o g e g r
200  # 3 1 8 4 2 1
201  # 2 6
202  #
203  byte = 0
204  if gruen == 0 or gruen == 1:
205  byte += 2
206  if gruen:
207  byte += 1
208  if gelb == 0 or gelb == 1:
209  byte += 8
210  if gelb:
211  byte += 4
212  if rot == 0 or rot == 1:
213  byte += 32
214  if rot:
215  byte += 16
216 
217  self.client.sendto(bytes([byte]), (self.servername, self.serverport))
218 
219 
220 def main():
221  """Hauptprogramm, über das Client und Server gestartet werden können, wenn
222  das Modul ausgeführt wird.
223  """
224  import sys
225 
226  if len(sys.argv) >= 2:
227  hostname = input("Hostname (Enter für localhost):")
228  if hostname == '':
229  hostname = 'localhost'
230  port = input("Port (Enter für 9999):")
231  if port == '':
232  port = '9999'
233 
234  if sys.argv[1] == "startserver":
235  print("Starte Server auf", hostname, "auf Port", port)
236  easerver = EAModulServer(hostname, int(port))
237  easerver.serve_forever()
238 
239  elif sys.argv[1] == "startclient":
240  print("Starte Client")
241  client = EAModulClient(hostname, int(port))
242 
243  print("""
244  Welche LEDs sollen angeschaltet werden? Gib drei Werte (0 oder 1)
245  ein (erst rot, dann gelb, dann grün)
246  Beispiel: 010 schaltet gelb an und rot und grün aus.
247 
248  Wenn Werte ungleich 0 der 1 verwendet werden, so bleibt die LED in
249  ihrem bisherigen Zustand: 050 schaltet rot und grün aus und belässt
250  gelb im bisherigen Zustand.
251 
252  'q' beendet das Programm""")
253 
254  while True:
255  eingabe = input()
256  if eingabe == 'q':
257  exit(0)
258 
259  try:
260  rot = int(eingabe[0])
261  gelb = int(eingabe[1])
262  gruen = int(eingabe[2])
263  client.sende(rot, gelb, gruen)
264 
265  except IndexError:
266  print("Eingabe fehlerhaft. Erwarte genau drei Zahlen (0 oder 1).")
267  print("Bitte wiederholen!")
268 
269  else:
270  print("Befehl angeben: startserver oder startclient")
271 
272 
273 # Main
274 #
275 if __name__ == "__main__":
276  main()
Definition: hw.py:1