├── __init__.py
├── modules
├── __init__.py
├── linphone
│ ├── __init__.py
│ └── Wrapper.py
├── pjsip
│ ├── __init__.py
│ ├── SipAccountHandler.py
│ ├── SipTelephoneHandler.py
│ └── SipClient.py
├── Webserver.py
├── Ringtone.py
└── RotaryDial.py
├── web
└── index.html
├── phone.jpg
├── ringtones
├── roh.wav
├── dialtone.wav
├── shutdown1.wav
├── startup1.wav
├── aselektriskbureau.wav
├── aselektriskbureau2.wav
└── Old_North_American_busy_signal.wav
├── configuration.yml
├── README.md
├── TelephoneDaemon.py
└── LICENSE
/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/modules/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/modules/linphone/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/modules/pjsip/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
AS Elektrisk Bureau - iptelefon
2 |
--------------------------------------------------------------------------------
/phone.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hnesland/aselektriskbureau/HEAD/phone.jpg
--------------------------------------------------------------------------------
/ringtones/roh.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hnesland/aselektriskbureau/HEAD/ringtones/roh.wav
--------------------------------------------------------------------------------
/ringtones/dialtone.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hnesland/aselektriskbureau/HEAD/ringtones/dialtone.wav
--------------------------------------------------------------------------------
/ringtones/shutdown1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hnesland/aselektriskbureau/HEAD/ringtones/shutdown1.wav
--------------------------------------------------------------------------------
/ringtones/startup1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hnesland/aselektriskbureau/HEAD/ringtones/startup1.wav
--------------------------------------------------------------------------------
/ringtones/aselektriskbureau.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hnesland/aselektriskbureau/HEAD/ringtones/aselektriskbureau.wav
--------------------------------------------------------------------------------
/ringtones/aselektriskbureau2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hnesland/aselektriskbureau/HEAD/ringtones/aselektriskbureau2.wav
--------------------------------------------------------------------------------
/ringtones/Old_North_American_busy_signal.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hnesland/aselektriskbureau/HEAD/ringtones/Old_North_American_busy_signal.wav
--------------------------------------------------------------------------------
/modules/pjsip/SipAccountHandler.py:
--------------------------------------------------------------------------------
1 | import pjsua as pj
2 |
3 | class SipAccountHandler(pj.AccountCallback):
4 | def __init__(self, account = None):
5 | pj.AccountCallback.__init__(self, account)
6 |
7 | def on_incoming_call(self, call):
8 | call.hangup(501, "")
9 |
10 | def on_reg_state(self):
11 | print "Registering: %s (%s" % (self.account.info().reg_status, self.account.info().reg_reason)
12 |
--------------------------------------------------------------------------------
/configuration.yml:
--------------------------------------------------------------------------------
1 | soundfiles:
2 | startup: /home/pi/telefon/ringtones/startup1.wav
3 | shutdown: /home/pi/telefon/ringtones/shutdown1.wav
4 | ringtone: /home/pi/telefon/ringtones/aselektriskbureau.wav
5 | busytone: /home/pi/telefon/ringtones/Old_North_American_busy_signal.wav
6 | timeout: /home/pi/telefon/ringtones/roh.wav
7 | dialtone: /home/pi/telefon/ringtones/dialtone.wav
8 |
9 | sip:
10 | username: sip_username
11 | password: sip_password
12 | hostname: sip.example.com
13 | port: 5060
14 |
--------------------------------------------------------------------------------
/modules/pjsip/SipTelephoneHandler.py:
--------------------------------------------------------------------------------
1 | import pjsua as pj
2 |
3 | class SipTelephoneHandler(pj.CallCallback):
4 | def __init__(self, call = None):
5 | pj.CallCallback.__init__(self, call)
6 |
7 | def on_state(self):
8 | print "Call is %s" % self.call.info().state_text
9 | print "Last code = %s" % self.call.info().last_code
10 | print "Last reason = %s" % self.call.info().last_reason
11 |
12 | def on_media_state(self):
13 | if self.call.info().media_state == pj.MediaState.ACTIVE:
14 | call_slot = self.call.info().conf_slot
15 | global TDaemon
16 | TDaemon.SipClient.pjlib.conf_connect(call_slot, 0)
17 | self.pjlib.conf_connect(0, call_slot)
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Software for AS Elektrisk Bureau
2 |
3 | This Python-script integrates the old rotary dial and handset on the
4 | AS Elektrisk Bureau pulse phone to use SIP.
5 |
6 | It uses GPIO on Raspberry Pi to communicate with the rotary dial, and
7 | the onboard soundcard for ringtone. An USB-soundcard is used for mic
8 | and handset audio.
9 |
10 | Some configuration on the Raspberry Pi is needed to make everything work,
11 | including installing the dependencies and configuring PulseAudio and ALSA
12 | to enable software mixing of sounds.
13 |
14 | Linphone (linphonec for console) is also required because it handles the
15 | SIP-connection.
16 |
17 | There is some code to use Pjsip, but it's not finished.
18 |
19 | For more information on the build, see http://imgur.com/a/HECDL/.
20 |
21 | 
22 |
--------------------------------------------------------------------------------
/modules/Webserver.py:
--------------------------------------------------------------------------------
1 | import tornado.web
2 | import os.path
3 | from tornado.options import define, options, parse_command_line
4 |
5 | define("port", default=8888, help="run on the given port", type=int)
6 | define("debug", default=False, help="run in debug mode")
7 |
8 | class MainHandler(tornado.web.RequestHandler):
9 | def get(self):
10 | self.render("../web/index.html")
11 |
12 | class Webserver:
13 | app = None
14 | Telephone = None
15 |
16 | def __init__(self, Telephone):
17 | parse_command_line()
18 |
19 | self.Telephone = Telephone
20 |
21 | self.app = tornado.web.Application(
22 | [
23 | (r"/", MainHandler)
24 | ]
25 | )
26 |
27 | self.app.listen(options.port)
28 | tornado.ioloop.IOLoop.instance().start()
29 |
--------------------------------------------------------------------------------
/modules/pjsip/SipClient.py:
--------------------------------------------------------------------------------
1 | import pjsua as pj
2 | from SipAccountHandler import SipAccountHandler
3 | from SipTelephoneHandler import SipTelephoneHandler
4 |
5 | class SipClient:
6 | hostname = ""
7 |
8 | def __init__(self):
9 | self.pjlib = pj.Lib()
10 | self.pjlib.init(log_cfg = pj.LogConfig(level = 4, callback = self.Log))
11 | print self.pjlib.enum_snd_dev()
12 | self.pjlib.set_snd_dev(2,1)
13 | self.transport = self.pjlib.create_transport(pj.TransportType.UDP)
14 |
15 | def Connect(self, hostname, username, password):
16 | self.pjlib.start()
17 | self.hostname = hostname
18 | self.acc_cfg = pj.AccountConfig(hostname, username, password)
19 | self.acc_cb = SipAccountHandler()
20 | self.acc = self.pjlib.create_account(self.acc_cfg, cb = self.acc_cb)
21 |
22 | def Dial(self, number):
23 | destination = "sip:%s@%s" % (number, self.hostname)
24 | self.current_call = self.acc.make_call(destination, SipTelephoneHandler())
25 |
26 | def Stop(self):
27 | self.pjlib.destroy()
28 | self.pjlib = None
29 |
30 | def Log(self, level, str, len):
31 | print "[DEBUG] %s" % str.rstrip("\n")
32 |
33 |
--------------------------------------------------------------------------------
/modules/linphone/Wrapper.py:
--------------------------------------------------------------------------------
1 | import sys, os
2 | from subprocess import Popen, PIPE
3 | import thread, threading
4 |
5 | class Wrapper(threading.Thread):
6 | linphone = None
7 | linphone_cmd = ["linphonec"]
8 |
9 | sip_username = None
10 | sip_hostname = None
11 | sip_password = None
12 |
13 | def __init__(self):
14 | threading.Thread.__init__(self)
15 |
16 | def IsRunning(self):
17 | try:
18 | return True if self.linphone.poll() is None else False
19 | except AttributeError:
20 | return False
21 |
22 | def StartLinphone(self):
23 | if not self.IsRunning():
24 | self.linphone = Popen(self.linphone_cmd, stdin=PIPE, stdout=PIPE)
25 |
26 | def StopLinphone(self):
27 | if self.IsRunning():
28 | self.linphone.terminate()
29 |
30 | def RegisterCallbacks(self, OnIncomingCall, OnOutgoingCall, OnRemoteHungupCall, OnSelfHungupCall):
31 | self.OnIncomingCall = OnIncomingCall
32 | self.OnOutgoingCall = OnOutgoingCall
33 | self.OnRemoteHungupCall = OnRemoteHungupCall
34 | self.OnSelfHungupCall = OnSelfHungupCall
35 |
36 | def run(self):
37 | while self.IsRunning():
38 | line = self.linphone.stdout.readline().rstrip()
39 | print "[LINPHONE] %s" % line
40 | if line.find("is contacting you") != -1:
41 | self.OnIncomingCall()
42 | if line.find("Call terminated") != -1:
43 | self.OnRemoteHungupCall()
44 | if line.find("Call ended") != -1:
45 | self.OnSelfHungupCall()
46 |
47 | def SendCmd(self, cmd):
48 | if self.IsRunning():
49 | self.linphone.stdin.write("".join([cmd, '\n']))
50 |
51 | def SipRegister(self, username, hostname, password):
52 | if self.IsRunning():
53 | self.sip_username = username
54 | self.sip_hostname = hostname
55 | self.sip_password = password
56 | self.SendCmd("register sip:%s@%s %s %s" % (username, hostname, hostname, password))
57 | #self.SendCmd("codec disable 4")
58 | #self.SendCmd("codec disable 5")
59 |
60 | def SipCall(self, number):
61 | if self.IsRunning():
62 | self.SendCmd("call sip:%s@%s" % (number, self.sip_hostname))
63 |
64 | def SipHangup(self):
65 | if self.IsRunning():
66 | self.SendCmd("terminate")
67 |
68 | def SipAnswer(self):
69 | if self.IsRunning():
70 | self.SendCmd("answer")
71 |
72 |
--------------------------------------------------------------------------------
/modules/Ringtone.py:
--------------------------------------------------------------------------------
1 | from threading import Timer
2 | import time
3 | import alsaaudio
4 | import wave
5 |
6 | class Ringtone:
7 | shouldring = 0
8 | ringtone = None
9 | ringfile = None
10 |
11 | ringstart = 0
12 |
13 | shouldplayhandset = 0
14 | handsetfile = None
15 | timerHandset = None
16 |
17 | config = None
18 |
19 | def __init__(self, config):
20 | self.config = config
21 |
22 | def start(self):
23 | self.shouldring = 1
24 | self.ringtone = Timer(0, self.doring)
25 | self.ringtone.start()
26 | self.ringstart = time.time()
27 |
28 | def stop(self):
29 | self.shouldring = 0
30 | if self.ringtone is not None:
31 | self.ringtone.cancel()
32 |
33 | def starthandset(self, file):
34 | self.shouldplayhandset = 1
35 | self.handsetfile = file
36 | if self.timerHandset is not None:
37 | print "[RINGTONE] Handset already playing?"
38 | return
39 |
40 | self.timerHandset = Timer(0, self.playhandset)
41 | self.timerHandset.start()
42 |
43 | def stophandset(self):
44 | self.shouldplayhandset = 0
45 | if self.timerHandset is not None:
46 | self.timerHandset.cancel()
47 | self.timerHandset = None
48 |
49 | def playhandset(self):
50 | print "Starting dialtone"
51 | wv = wave.open(self.handsetfile)
52 | device = alsaaudio.PCM(card="plug:external")
53 | #device.setchannels(wv.getnchannels())
54 | #device.setrate(wv.getframerate())
55 | #device.setperiodsize(320)
56 |
57 | data = wv.readframes(320)
58 | while data and self.shouldplayhandset:
59 | device.write(data)
60 | data = wv.readframes(320)
61 | wv.rewind()
62 | wv.close()
63 |
64 |
65 | def playfile(self, file):
66 | wv = wave.open(file)
67 | self.device = alsaaudio.PCM(card="pulse")
68 | self.device.setchannels(wv.getnchannels())
69 | self.device.setrate(wv.getframerate())
70 | self.device.setperiodsize(320)
71 |
72 | data = wv.readframes(320)
73 | while data:
74 | self.device.write(data)
75 | data = wv.readframes(320)
76 | wv.rewind()
77 | wv.close()
78 |
79 | def doring(self):
80 | if self.ringfile is not None:
81 | self.ringfile.rewind()
82 | else:
83 | self.ringfile = wave.open(self.config["soundfiles"]["ringtone"], 'rb')
84 | self.device = alsaaudio.PCM(card="pulse")
85 | self.device.setchannels(self.ringfile.getnchannels())
86 | self.device.setrate(self.ringfile.getframerate())
87 | self.device.setperiodsize(320)
88 |
89 |
90 | while self.shouldring:
91 | data = self.ringfile.readframes(320)
92 | while data:
93 | self.device.write(data)
94 | data = self.ringfile.readframes(320)
95 |
96 | self.ringfile.rewind()
97 | time.sleep(2)
98 | if time.time() - 60 > self.ringstart:
99 | self.stop()
100 |
--------------------------------------------------------------------------------
/modules/RotaryDial.py:
--------------------------------------------------------------------------------
1 | # Rotary Dial Parser
2 | # Expects the following hardware rules:
3 | # 1 is 1 pulse
4 | # 9 is 9 pulses
5 | # 0 is 10 pulses
6 |
7 | import RPi.GPIO as GPIO
8 | from threading import Timer
9 | import time
10 |
11 | class RotaryDial:
12 |
13 | # We'll be reading BCM GPIO 4 (pin 7 on board)
14 | pin_rotary = 4
15 |
16 | # We'll be reading on/off hook events from BCM GPIO 3
17 | pin_onhook = 3
18 |
19 | # After 900ms, we assume the rotation is done and we get
20 | # the final digit.
21 | digit_timeout = 0.9
22 |
23 | # We keep a counter to count each pulse.
24 | current_digit = 0
25 |
26 | # Simple timer for handling the number callback
27 | number_timeout = None
28 |
29 | last_input = 0
30 |
31 | # Timer to ensure we're on hook
32 | onhook_timer = None
33 | should_verify_hook = True
34 |
35 | def __init__(self):
36 | # Set GPIO mode to Broadcom SOC numbering
37 | GPIO.setmode(GPIO.BCM)
38 |
39 | # Listen for rotary movements
40 | GPIO.setup(self.pin_rotary, GPIO.IN)
41 | GPIO.add_event_detect(self.pin_rotary, GPIO.BOTH, callback = self.NumberCounter)
42 |
43 | # Listen for on/off hooks
44 | GPIO.setup(self.pin_onhook, GPIO.IN)
45 | GPIO.add_event_detect(self.pin_onhook, GPIO.BOTH, callback = self.HookEvent, bouncetime=100)
46 |
47 | self.onhook_timer = Timer(2, self.verifyHook)
48 | self.onhook_timer.start()
49 |
50 | # Handle counting of rotary movements and respond with digit after timeout
51 | def NumberCounter(self, channel):
52 | input = GPIO.input(self.pin_rotary)
53 | #print "[INPUT] %s (%s)" % (input, channel)
54 | if input and not self.last_input:
55 | self.current_digit += 1
56 |
57 | if self.number_timeout is not None:
58 | self.number_timeout.cancel()
59 |
60 | self.number_timeout = Timer(self.digit_timeout, self.FoundNumber)
61 | self.number_timeout.start()
62 | self.last_input = input
63 | # time.sleep(0.002)
64 |
65 | # Wrapper around the off/on hook event
66 | def HookEvent(self, channel):
67 | input = GPIO.input(self.pin_onhook)
68 | if input:
69 | self.hook_state = 1
70 | self.OffHookCallback()
71 | else:
72 | self.hook_state = 0
73 | self.OnHookCallback()
74 |
75 | def StopVerifyHook(self):
76 | self.should_verify_hook = False
77 |
78 | def verifyHook(self):
79 | while self.should_verify_hook:
80 | state = GPIO.input(self.pin_onhook)
81 | self.OnVerifyHook(state)
82 | time.sleep(1)
83 |
84 | # When the rotary movement has timed out, we callback with the final digit
85 | def FoundNumber(self):
86 | if self.current_digit == 10:
87 | self.current_digit = 0
88 | self.NumberCallback(self.current_digit)
89 | self.current_digit = 0
90 |
91 | # Handles the callbacks we're supplying
92 | def RegisterCallback(self, NumberCallback, OffHookCallback, OnHookCallback, OnVerifyHook):
93 | self.NumberCallback = NumberCallback
94 | self.OffHookCallback = OffHookCallback
95 | self.OnHookCallback = OnHookCallback
96 | self.OnVerifyHook = OnVerifyHook
97 |
98 | input = GPIO.input(self.pin_onhook)
99 | if input:
100 | self.OffHookCallback()
101 | else:
102 | self.OnHookCallback()
103 |
--------------------------------------------------------------------------------
/TelephoneDaemon.py:
--------------------------------------------------------------------------------
1 | import os
2 | import Queue
3 | import threading
4 | import signal
5 | import sys
6 | import yaml
7 |
8 | from threading import Timer
9 | from modules.Ringtone import Ringtone
10 | from modules.RotaryDial import RotaryDial
11 | from modules.Webserver import Webserver
12 | from modules.linphone import Wrapper
13 | # alternative SIP-implementation
14 | #from modules.pjsip.SipClient import SipClient
15 |
16 | callback_queue = Queue.Queue()
17 |
18 | class TelephoneDaemon:
19 | # Number to be dialed
20 | dial_number = ""
21 |
22 | # On/off hook state
23 | offHook = False
24 |
25 | # Off hook timeout
26 | offHookTimeoutTimer = None
27 |
28 | RotaryDial = None
29 | SipClient = None
30 | WebServer = None
31 |
32 | config = None
33 |
34 | def __init__(self):
35 | print "[STARTUP]"
36 |
37 | self.config = yaml.load(file("configuration.yml",'r'))
38 |
39 | signal.signal(signal.SIGINT, self.OnSignal)
40 |
41 | # Ring tone
42 | self.Ringtone = Ringtone(self.config)
43 |
44 | # This is to indicate boot complete. Not very realistic, but fun.
45 | #self.Ringtone.playfile(config["soundfiles"]["startup"])
46 |
47 | # Rotary dial
48 | self.RotaryDial = RotaryDial()
49 | self.RotaryDial.RegisterCallback(NumberCallback = self.GotDigit, OffHookCallback = self.OffHook, OnHookCallback = self.OnHook, OnVerifyHook = self.OnVerifyHook)
50 |
51 | self.SipClient = Wrapper.Wrapper()
52 | self.SipClient.StartLinphone()
53 | self.SipClient.SipRegister(self.config["sip"]["username"], self.config["sip"]["hostname"], self.config["sip"]["password"])
54 | self.SipClient.RegisterCallbacks(OnIncomingCall = self.OnIncomingCall, OnOutgoingCall = self.OnOutgoingCall, OnRemoteHungupCall = self.OnRemoteHungupCall, OnSelfHungupCall = self.OnSelfHungupCall)
55 |
56 | # Start SipClient thread
57 | self.SipClient.start()
58 |
59 | # Web interface to enable remote configuration and debugging.
60 | self.Webserver = Webserver(self)
61 |
62 | raw_input("Waiting.\n")
63 |
64 | def OnHook(self):
65 | print "[PHONE] On hook"
66 | self.offHook = False
67 | self.Ringtone.stophandset()
68 | # Hang up calls
69 | if self.SipClient is not None:
70 | self.SipClient.SipHangup()
71 |
72 | def OffHook(self):
73 | print "[PHONE] Off hook"
74 | self.offHook = True
75 | # Reset current number when off hook
76 | self.dial_number = ""
77 |
78 |
79 | self.offHookTimeoutTimer = Timer(5, self.OnOffHookTimeout)
80 | self.offHookTimeoutTimer.start()
81 |
82 | # TODO: State for ringing, don't play tone if ringing :P
83 | print "Try to start dialtone"
84 | self.Ringtone.starthandset(self.config["soundfiles"]["dialtone"])
85 |
86 | self.Ringtone.stop()
87 | if self.SipClient is not None:
88 | self.SipClient.SipAnswer()
89 |
90 | def OnVerifyHook(self, state):
91 | if not state:
92 | self.offHook = False
93 | self.Ringtone.stophandset()
94 |
95 | def OnIncomingCall(self):
96 | print "[INCOMING]"
97 | self.Ringtone.start()
98 |
99 | def OnOutgoingCall(self):
100 | print "[OUTGOING] "
101 |
102 | def OnRemoteHungupCall(self):
103 | print "[HUNGUP] Remote disconnected the call"
104 | # Now we want to play busy-tone..
105 | self.Ringtone.starthandset(self.config["soundfiles"]["busytone"])
106 |
107 | def OnSelfHungupCall(self):
108 | print "[HUNGUP] Local disconnected the call"
109 |
110 | def OnOffHookTimeout(self):
111 | print "[OFFHOOK TIMEOUT]"
112 | #self.Ringtone.stophandset()
113 | #self.Ringtone.starthandset(self.config["soundfiles"]["timeout"])
114 |
115 | def GotDigit(self, digit):
116 | print "[DIGIT] Got digit: %s" % digit
117 | self.Ringtone.stophandset()
118 | self.dial_number += str(digit)
119 | print "[NUMBER] We have: %s" % self.dial_number
120 |
121 | # Shutdown command, since our filesystem isn't read only (yet?)
122 | # This hopefully prevents dataloss.
123 | # TODO: stop rebooting..
124 | if self.dial_number == "0666":
125 | self.Ringtone.playfile(self.config["soundfiles"]["shutdown"])
126 | os.system("halt")
127 |
128 | if len(self.dial_number) == 8:
129 | if self.offHook:
130 | print "[PHONE] Dialing number: %s" % self.dial_number
131 | self.SipClient.SipCall(self.dial_number)
132 | self.dial_number = ""
133 |
134 | def OnSignal(self, signal, frame):
135 | print "[SIGNAL] Shutting down on %s" % signal
136 | self.RotaryDial.StopVerifyHook()
137 | self.SipClient.StopLinphone()
138 | sys.exit(0)
139 |
140 | def main():
141 | TDaemon = TelephoneDaemon()
142 |
143 | if __name__ == "__main__":
144 | main()
145 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------