webXMPP

Check-in [b40feebcbe]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Bringup
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | master | trunk
Files: files | file ages | folders
SHA3-256:b40feebcbedbb743a376f0fa2d40cef636f0a26679cb26a4b386821638454550
User & Date: vandys 2018-09-22 01:08:49
Context
2018-09-22
04:01
Deal with punctuation in message text. check-in: 8d7ea3e3f0 user: vandys tags: master, trunk
01:08
Bringup check-in: b40feebcbe user: vandys tags: master, trunk
2018-09-20
00:04
First code-up, notifications onto a Ham repeater announcement check-in: f9faa86edd user: vandys tags: master, trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to tools/ham.py.

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

































27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61






62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

93
94


95
96
97
98
99
100
101

102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178



179

180
#  a wav file (using flite) and keys up a transmitter and speaks them
#  over the air.
#
# In my case, it keys an actual repeater, which takes care of ID'ing.
#  Otherwise you'll need to change this to include your ID when
#  pushing out this telemetry.
#
import sys, time
from chore import pong, config
import pdb

# Initial condition, no events ever seen
Gen = 0

# Hook for logging
Debug = True
def log(s):
    if Debug:
	sys.stderr.write(s)
	sys.stderr.write('\n')


































# Configuration, indented chore style
Cfg = None
def load_cfg(fn):
    global Cfg
    c = config.Config()

    # Notification server out on the Internet
    #
    # server <hostname>
    #  port X
    #  user name-on-server
    #  password password-for-name
    c.onearg.add( ("server",) )
    c.ints.add( ("server", "port") )
    c.onearg.add( ("server", "user") )
    c.onearg.add( ("server", "password") )

    # I/O line controller, probably local LAN
    #
    # rig <hostname>
    #  port Y
    #  channel Z
    #  audio "audio-device-name"
    c.args.add( ("rig",) )
    c.ints.add( ("rig", "port") )
    c.onearg.add( ("rig", "channel") )
    c.onearg.add( ("rig", "audio") )

    # Parse and set config dict
    Cfg = c.load_cfg(fn)

# New notifications contained in this packet
def notify(pak):
    global Gen, Debug







    inner = pak.inner
    log("Notification: gen %d -> %d\n" % (gen, inner["gen"]))
    for tup in inner["msgs"]:
        lt = len(tup)

        # Ignore mirrors of our own sends on other devices
        if lt and (not tup[0]):
            continue

        # No details at all, so just show one notification
        if lt in (0, 1):
            n1 = "New Message"
            n2 = None

        # Just who
        elif lt == 2:
            n1 = "New Message"
            n2 = tup[1]

        # Who plus headline 
        elif lt == 3:
            n1 = tup[1]
            n2 = tup[2]

        # Who plus headline plus body
        else:
            n1 = tup[1] + ": " + tup[2]
            n2 = tup[3]

	# Construct what to say
	pdb.set_trace()

	n = "Notification%s: %s" % \
	    ((" from %s" % (n2,)) if n2 else "", n1)


	adev = Cfg["rig"][1]["audio"][0]
	destfn = "/tmp/notify%d.wav" % (os.getpid(),)
	os.system("flite -t '%s' %s" % (n, destfn))

	# Key up the rig
	tx_on()
	time.sleep(1.0)

	# Say the message
	os.system("aplay -D '%s' %s" % (adev, destfn))
	time.sleep(0.5)
	tx_off()

# Endless execution, notification client
def run():
    global Cfg, Gen

    # Get a wrapper for our pong network connection
    pdb.set_trace()
    conn = pong.Client(cfg.server, cfg.port, cfg.user, cfg.password)

    # We need a send-receive-send pattern as a minimum to become
    #  an assured/streaming UDP "connection".  This loop starts
    #  a new socket, does a param get/got, and then a timed
    #  get.  At the timeout, the server provides a "got", after
    #  which we send another "get".  This keeps the firewall/NAT
    #  state alive indefinitely, at a cost of one transmit and
    #  one receive every 2.5 minutes.
    # When we hit a network error (usually due to change in our
    #  IP address), we reset the connection and start over with
    #  the param get/got.

    while True:
        # Params; we in particular need to know the server's
        #  intended timeout.
        while True:
            pak = conn.pingpong(conn.msg("params", "get"))
            if pak is not None:
                break
            # Note that on error, the "pong" library will already
            #  have closed out the socket
            time.sleep(pong.WAITNET)

        # This is how long they'll hold a notify/get pending before
        #  sending back a null result.
        # If we request one and don't hear back in this amount of
        #  time, we have a lost packet or something.
        # 1.1 is our 10% slop factor over the expected timeout
        #  from the server.
        tmo = pak.inner["timeout"] * 1.1

        # Server loop
        while True:
            # Always yield for a second, so no matter
            #  what we never CPU spin hard.
            time.sleep(1)

            # Next round of notifications
            # Request events starting at this serial number
            pak = conn.msg("notify", "get", {
                "gen": Gen, "detail": 3, "nmsg": 2})
            pak.who = dest
            resp = conn.ping_pong(pak, tmo)

            # Failure
            if resp is None:
                # We drop out of the server loop, and start over
                #  with a fresh socket and param get/got
                time.sleep(pong.WAITNET)
                break

            # Nothing happened
            if resp.inner["gen"] == gen:
                continue

            # New messages
            notify(resp)
            gen = resp.inner["gen"]

if __name__ == "__main__":

    # Usage
    if len(sys.argv) != 1:
	sys.stderr.write("Usage is: %s <config>\n" % (sys.argv[0],))

    # Config and run



    load_cfg(sys.argv[1])

    run()







|



<
<
<







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
<
|
<
|

|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|

|
|

|
|
<

>
>
>
>
>
>
|
|
|
|

|
|
|

|
|
|
|

|
|
|
|

|
|
|
|

|
|
|
|

|
<
>
|
<
>
>
|
|
|

|
|
|
>
|
|
|
|

|
|
<

<
<
<
<
|
|
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|

|
|
|
|
|

|
|
|
|
<
|

|
|
|
|
|
|

|
|
|

|
|
|








>
>
>
|
>
|
6
7
8
9
10
11
12
13
14
15
16



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

58

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

125
126

127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143

144




145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#  a wav file (using flite) and keys up a transmitter and speaks them
#  over the air.
#
# In my case, it keys an actual repeater, which takes care of ID'ing.
#  Otherwise you'll need to change this to include your ID when
#  pushing out this telemetry.
#
import os, sys, time, socket, json
from chore import pong, config
import pdb




# Hook for logging
Debug = True
def log(s):
    if Debug:
	sys.stderr.write(s)
	sys.stderr.write('\n')

class HamNotifier(object):
    def __init__(self, cfgfn):
	# Notification generation counter
	self.gen = 0

	# Dampen initial notifications, where we're just starting
	#  and catching up
	self.starttm = time.time() + 10.0

	# Parse config file
	self.load_cfg(cfgfn)

	# Handle for communication with notification server
	cfg = self.cfg
	shost,scfg = cfg["server"]
	self.nconn = pong.Client(shost,
	    scfg["port"], scfg["user"], scfg["password"])

	# Socket for controlling repeater transmitter
	self.rconn = s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	rhost,rigcfg = cfg["rig"]
	rport = rigcfg["port"]
	self.raddr = (rhost, rport)
	rchan = rigcfg["channel"][0].upper()
	self.rtxon = json.dumps({"chan": rchan, "val": 1})
	self.rtxoff = json.dumps({"chan": rchan, "val": 0})

    # Transmitter control
    def tx_on(self):
	self.rconn.sendto(self.rtxon, self.raddr)
    def tx_off(self):
	self.rconn.sendto(self.rtxoff, self.raddr)

    # Configuration, indented chore style

    def load_cfg(self, fn):

	c = config.Config()

	# Notification server out on the Internet
	#
	# server <hostname>
	#  port X
	#  user name-on-server
	#  password password-for-name
	c.onearg.add( ("server",) )
	c.ints.add( ("server", "port") )
	c.onearg.add( ("server", "user") )
	c.onearg.add( ("server", "password") )

	# I/O line controller, probably local LAN
	#
	# rig <hostname>
	#  port Y
	#  channel Z
	#  audio "audio-device-name"
	c.onearg.add( ("rig",) )
	c.ints.add( ("rig", "port") )
	c.onearg.add( ("rig", "channel") )
	c.onearg.add( ("rig", "audio") )

	# Parse and set config dict
	self.cfg = c.load_cfg(fn)

    # New notifications contained in this packet
    def notify(self, pak):


	# Dampen initial rush of notifications as we catch up
	if self.starttm:
	    if time.time() < self.starttm:
		return
	    self.starttm = None

	inner = pak.inner
	log("Notification: gen %d -> %d\n" % (self.gen, inner["gen"]))
	for tup in inner["msgs"]:
	    lt = len(tup)

	    # Ignore mirrors of our own sends on other devices
	    if lt and (not tup[0]):
		continue

	    # No details at all, so just show one notification
	    if lt in (0, 1):
		n1 = "New Message"
		n2 = None

	    # Just who
	    elif lt == 2:
		n1 = "New Message"
		n2 = tup[1]

	    # Who plus headline 
	    elif lt == 3:
		n1 = tup[1]
		n2 = tup[2]

	    # Who plus headline plus body
	    else:
		n1 = tup[1] + ": " + tup[2]
		n2 = tup[3]

	    # Construct what to say

	    if not n2:
		n = "Notification: %s" % (n1,)

	    else:
		n = "Notification from %s: %s" % (n1, n2)
	    adev = self.cfg["rig"][1]["audio"]
	    destfn = "/tmp/notify%d.wav" % (os.getpid(),)
	    os.system("flite -t '%s' %s" % (n, destfn))

	    # Key up the rig
	    self.tx_on()
	    time.sleep(0.75)

	    # Say the message
	    os.system("aplay -D '%s' %s" % (adev, destfn))
	    time.sleep(0.5)
	    self.tx_off()

    # Endless execution, notification client
    def run(self):






	# We need a send-receive-send pattern as a minimum to become
	#  an assured/streaming UDP "connection".  This loop starts
	#  a new socket, does a param get/got, and then a timed
	#  get.  At the timeout, the server provides a "got", after
	#  which we send another "get".  This keeps the firewall/NAT
	#  state alive indefinitely, at a cost of one transmit and
	#  one receive every 2.5 minutes.
	# When we hit a network error (usually due to change in our
	#  IP address), we reset the connection and start over with
	#  the param get/got.
	conn = self.nconn
	while True:
	    # Params; we in particular need to know the server's
	    #  intended timeout.
	    while True:
		pak = conn.pingpong(conn.msg("params", "get"))
		if pak is not None:
		    break
		# Note that on error, the "pong" library will already
		#  have closed out the socket
		time.sleep(pong.WAITNET)

	    # This is how long they'll hold a notify/get pending before
	    #  sending back a null result.
	    # If we request one and don't hear back in this amount of
	    #  time, we have a lost packet or something.
	    # 1.1 is our 10% slop factor over the expected timeout
	    #  from the server.
	    tmo = pak.inner["timeout"] * 1.1

	    # Server loop
	    while True:
		# Always yield for a second, so no matter
		#  what we never CPU spin hard.
		time.sleep(1)

		# Next round of notifications
		# Request events starting at this serial number
		pak = conn.msg("notify", "get", {
		    "gen": self.gen, "detail": 3, "nmsg": 2})

		resp = conn.ping_pong(pak, tmo)

		# Failure
		if resp is None:
		    # We drop out of the server loop, and start over
		    #  with a fresh socket and param get/got
		    time.sleep(pong.WAITNET)
		    break

		# Nothing happened
		if resp.inner["gen"] == self.gen:
		    continue

		# New messages
		self.notify(resp)
		self.gen = resp.inner["gen"]

if __name__ == "__main__":

    # Usage
    if len(sys.argv) != 1:
	sys.stderr.write("Usage is: %s <config>\n" % (sys.argv[0],))

    # Config and run
    if len(sys.argv) == 1:
	cfgn = os.environ["HOME"] + "/.config/hamnotify.txt"
    else:
	cfgn = sys.argv[1]
    n = HamNotifier(cfgn)
    n.run()