Check-in [8d8ccc7f52]

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

Overview
Comment:Merged to trunk for next blog post release. This has the nominally working email when startled script that can be configured to either email a plot of SPL or an MP3 of the room audio around the event.
Timelines: family | ancestors | descendants | both | blog-splear09 | trunk
Files: files | file ages | folders
SHA1: 8d8ccc7f52b757d10112b15fed1d83e9b5f78f22
User & Date: rberteig 2015-06-16 22:18:12
Context
2015-06-25
21:36
Change scared delta SPL threshold to 12dB from 6dB. check-in: dd66d0e331 user: rberteig tags: trunk
2015-06-16
22:18
Merged to trunk for next blog post release. This has the nominally working email when startled script that can be configured to either email a plot of SPL or an MP3 of the room audio around the event. check-in: 8d8ccc7f52 user: rberteig tags: blog-splear09, trunk
22:11
Fixed double noise gate stages in the SoX filter pipeline, restored the companding stage that should have been after a single noise gate. Tweaked levels on the noise gate. Also merged the recorder into the main scared.lua script, so that it can send email when scared based on the GPIO pin signalling. The email has a noise gated companded and MP3 encoded copy of the audio from just before the trigger to some time after the trigger. MP3 is used because both Android and iPhone appear to be able to play it directly from an email attachment. Leaf check-in: 8b86ec7568 user: rberteig tags: scared
2015-05-22
23:23
Merge in the scared.lua scripts for the latest blog post. check-in: 26b540b327 user: rberteig tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Added Scared/pinmmap.lua.































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
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
local GPIO=require"GPIO"

GPIO.setmode(GPIO.BOARD)

--[[
table.foreach(GPIO,function(k,v)
  if type(v)=="string" or type(v)=="number" then print(k,v) end
end
)
--]]

i2c={sda=3,scl=5}
--GPIO.setup(i2c.sda,GPIO.I2C)
--GPIO.setup(i2c.scl,GPIO.I2C)

uart={tx=8,rx=10}
--GPIO.setup(uart.tx,GPIO.SERIAL)
--GPIO.setup(uart.rx,GPIO.SERIAL)

spi={mosi=19,miso=21,sclk=23}
--GIO.setup(spi.mosi, GPIO.SPI)
--GIO.setup(spi.miso, GPIO.SPI)
--GIO.setup(spi.sclk, GPIO.SPI)

--[[
pins={11,12,13,15,16,18,22,24,26}
for _,p in ipairs(pins) do 
  GPIO.setup(p,GPIO.IN)
end
for _,p in ipairs(pins) do 
  print(p, GPIO.input(p))
end
--]]

local b = false
--GPIO.setup(26,GPIO.IN,GPIO.PUD_UP)
GPIO.setup(26,GPIO.IN)
while(true) do
  --GPIO.wait_for_edge(26, GPIO.BOTH)
  local b0 = GPIO.input(26)
  if b0 ~= b then
    print(b0)
  end
  b = b0
end

local pinfuncs={}
pinfuncs[GPIO.IN] = "in"
pinfuncs[GPIO.OUT] = "out"
pinfuncs[GPIO.SPI] = "spi"
pinfuncs[GPIO.I2C] = "i2c"
pinfuncs[GPIO.PWM] = "pwm"
pinfuncs[GPIO.SERIAL] = "ser"
pinfuncs[GPIO.UNKNOWN] = "unk"
setmetatable(pinfuncs,{__index=function(k) return tostring(k) end})

pins={3,5,7,8,10}

for i=1,#pins do
 print(i, pins[i], pinfuncs[GPIO.gpio_function(pins[i])])
end

GPIO.cleanup()

Added Scared/pinsysfs.lua.























































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
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
local P = require "posix"

-- write str to the named file
function putfile(name,str)
  local f = assert(io.open(name,"w"))
  f:write(str)
  f:close()
end

-- read the named file and return its content
function getfile(name)
  local f = assert(io.open(name,"r"))
  local str f:read"*a"
  f:close()
  return str
end

-- transform a list into a set for quick lookup
function SET(t)
  local s = {}
  for _,v in ipairs(t) do
    s[v] = true
  end
  return s
end


-- container for GPIO functions
gpio={
  pins1_1 = SET{ -- Original RPi A, B
     0,  1,          4,          7,
     8,  9, 10, 11,         14, 15,
        17, 18,         21, 22, 23,
    24, 25,       },

  pins1_2 = SET{ -- Rev 2 A, B
             2,  3,  4,          7,
     8,  9, 10, 11,         14, 15,
        17, 18,             22, 23,
    24, 25,     27},

  pins2_0 = SET{ -- A+, B+, 2A, 2B
             2,  3,  4,  5,  6,  7,
     8,  9, 10, 11, 12, 13, 14, 15,
    16, 17, 18, 19, 20, 21, 22, 23,
    24, 25, 26, 27},
}

-- default to RPi B+ board
-- TODO: Read /proc/cpuinfo and get this right
gpio.pins = gpio.pins2_0

-- export a sysfs pin to userland
function gpio.export(pin)
  assert(gpio.pins[pin], "Can't use pin")
  putfile("/sys/class/gpio/export",tostring(pin))
end

-- return a sysfs pin to the kernel
function gpio.unexport(pin)
  assert(gpio.pins[pin], "Can't use pin")
  putfile("/sys/class/gpio/unexport",tostring(pin))
end

-- userland path to a specific pin's atribute file
function gpio.pinpath(pin,attr)
  return ("/sys/class/gpio/gpio%d/%s"):format(pin,attr)
end

-- set pin direction
function gpio.direction(pin,dir)
  local path = gpio.pinpath(pin, "direction")
print("Setting "..path.." to "..dir)
  putfile(path, dir)
end

-- set pin edge detector
function gpio.edge(pin,edge)
  local path = gpio.pinpath(pin, "edge")
print("Setting "..path.." to "..edge)
  putfile(path, edge)
end

-- blocking wait with timeout for an edge on a pin
function gpio.wait(pin)
  assert(gpio.pins[pin], "Can't use pin")
  local path = gpio.pinpath(pin, "value")
  if not gpio.pinfd then 
    gpio.pinfd = P.open(path, P.O_RDONLY)
  end
  local p = gpio.pinfd
  if not gpio.fds then gpio.fds = {} end
  if not gpio.fds[p] then gpio.fds[p] = {events={}, revents={}} end
  gpio.fds[p].events.PRI=true
  local ret = P.poll(gpio.fds, 10000)
  if ret == 0 then return 0 end
  P.lseek(p,0,P.SEEK_SET)
  ret = tonumber(P.read(p,8))
  --P.close(p)
  --gpio.pinfd = nil
  return ret
end 


-- Use pin P1-28 aka GPIO7 for SPLear alerts
gpio.export(7)
gpio.direction(7,"in")
gpio.edge(7,"rising")

while(true) do
  local r = gpio.wait(7)
  if r == 0 then
    --print"timeout"
  elseif r==1 then
    local tim = P.gettimeofday()
    print("eek "..tim.sec..'.'..tim.usec)
  else
    print("???", r)
  end
end

if gpio.pinfd then P.close(gpio.pinfd) end

Added Scared/record.lua.





































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
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
#! lua
local struct = require "struct"


-- today's date in (almost) ISO-8601 format
local function isodate() 
  return os.date"%Y%m%d-%H%M%S" 
end


-- Implement a buffer of samples. For simplicity, let the 
-- index increment without bound. Note that this will have
-- a problem after 2^53 samples, which is about 30 million 
-- years at 10 samples per second.
local recent = {
  depth=100,
  n=1,
  add=function(self,s)
    local n = self.n
    self[n] = s
    self[n-self.depth] = nil
    self.n = n + 1
  end,
  buffer=function(self)
    local n1,n2 = self.n-self.depth, self.n-1
    if n1 <= 0 or n2 <= 0 then return end
    return table.concat(self,'',self.n-self.depth,self.n-1)
  end
}

local function auheader(buf, rate)
  local fmt = ">c4LLLLLL"
  local hdr = struct.pack(fmt,
      ".snd",                   -- 0 magic number
      struct.size(fmt),         -- 1 data offset
      #buf,                     -- 2 length or -1
      1,                        -- 3 encoding uLaw
      rate,                     -- 4 8152 Hz sample rate
      1,                        -- 5 mono
      0)                        -- 6 empty metadata string
  return hdr
end

local function writeau(au, buf)
  local f = assert(io.open(au,"wb"))
  f:write(auheader(buf, 8152), buf)
  f:close()
end

function writemp3(name, udata)
    local soxgate = 'compand .1,.2 -inf,-55.1,-inf,-55,-55 0 -90 .1 '
    local soxcompand = 'compand 0.3,1 6:-70,-60,-20 -5 -90 0.2 '
    local soxcmd = 'sox -t au - -t wav -b 16 - '
           .. soxgate
           .. soxcompand
           ..'| lame - '..name
    print(soxcmd)
    local fp = assert(io.popen('sh -c "'..soxcmd..'"', 'w'))
    fp:write(auheader(udata, 8152), udata)
    fp:close()
end

-- Quick and dirty way to force the UART to have the 
-- tty driver settings we need for clean raw access
-- to the port.
os.execute("stty -F /dev/ttyAMA0 115200"
	.." pass8 raw -iexten"
	.." -echo -echoe -echok -echoke -echoctl")

-- Open the UART for both reading and writing, and send
-- the SPLear command to go into uLaw recording mode. The
-- output must be unbuffered because the current SPLear 
-- firmware would revert to 0.5 Hz summary mode if it
-- receives a newline.
local port = assert(io.open('/dev/ttyAMA0','r+'))
port:setvbuf"no"   -- no output buffer at all
port:write"R"      -- so this write is immediate

-- Loop reading the audio stream and watching for loud
-- noises, keeping a log of recent samples as we go.
-- When a loud noise is heard, keep logging for a bit
-- before sending email. Include the log in the email.

while true do
  local buf = port:read(1000) -- sampled at 8153 Hz probably
  recent:add(buf)
  if recent.n % 8 == 0 then
    io.write(tostring(recent.n),"\r")
    io.flush()
  end
  if recent.n > recent.depth then
    local eek = "rec-"..isodate()
    --writeau(eek..".au", recent:buffer())
    writemp3(eek..".mp3", recent:buffer())
    return
  end
end

Changes to Scared/scared.lua.

1

2

3

4


5
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
#! lua

local plot = require "plotspl"



address = "user@example.com"



-- today's date in (almost) ISO-8601 format
local function isodate() 
  return os.date"%Y-%m-%d %H:%M:%S" 
end






-- make its argument be a single string
function flatten(s)
  if type(s)=="string" then return '"'..s..'"' end
  if type(s)~="table" then return nil end
  return '"'..table.concat(s, '" "')..'"'
end

-- Send an email to addr with subject and body. If body
-- is nil then just repeat the subject. This assumes that
-- a "mail" command is available with the usual options.
local function email(addr, subj, body)
  body = body or subj
  local mail = ("mail -s '%q' %s"):format(subj,addr)
  local b = assert(io.popen(mail, "w"))
  b:write([[I heard a noise on ]], isodate(), "\r\n\r\n")
  b:write(body, "\r\n\r\n")
  b:write"(This an automated message.)\r\n"
  b:close()
end

-- Send a MIME packed email containing an image of the
-- event to a recipient with a subject
-- mpack(address, eek, "scared.png")
local function mpack(address, subject, pngfile)
  assert(address)
  assert(subject)
  assert(pngfile)
  local cmd = ("mpack -a -s %q %q %s"):format(subject, pngfile, address)
  print(cmd)
  os.execute(cmd)
end

-- Implement a buffer of samples. For simplicity, let the 
-- index increment without bound. Note that this will have
-- a problem after 2^53 samples, which is about 30 million 
-- years at 10 samples per second.

local recent = {
  depth=200,
  n=1,













  add=function(self,spl)
    local n = self.n
    self[n] = spl
    self[n-self.depth] = nil
    self.n = n + 1
  end,


  text=function(self)






    local t = {"0.1s, dB"}
    local offset = self.n-self.depth-1
    for i=1,self.depth do
      if self[i+offset] then
        t[#t+1] = ("%0.1f, %s"):format(i/10,self[i+offset])
      end
    end
    return table.concat(t,"\n")
  end
}

































-- Quick and dirty way to force the UART to have the 
-- tty driver settings we need for clean raw access
-- to the port.
os.execute("stty -F /dev/ttyAMA0 115200"
	.." pass8 raw -iexten"
	.." -echo -echoe -echok -echoke -echoctl")

-- Open the UART for both reading and writing, and send
-- the SPLear command to go into raw SPL sample mode. The
-- output must be unbuffered because the current SPLear 
-- firmware would revert to 0.5 Hz summary mode if it
-- receives a newline.
local port = assert(io.open('/dev/ttyAMA0','r+'))
port:setvbuf"no"   -- no output buffer at all
port:write"S"      -- so this write is immediate

-- Loop reading the SPL samples and watching for loud
-- noises, keeping a log of recent samples as we go.
-- When a loud noise is heard, keep logging for a bit
-- before sending email. Include the log in the email.
local spl, spl0 
local triggered = false
local eek = ""
local addr = flatten(address)
print("Notifying "..addr)




















































































while true do
  local line = port:read"*l" -- read a single line
  spl = tonumber(line)
  if spl then
    spl = 0.75 * spl
    io.write("SPL "..(triggered and "TRG " or "    "), spl, "\r")
    recent:add(spl)

    -- Watch for the trigger condition: 12 dB louder 
    if not triggered and spl0 and (spl - spl0 > 12.) then
      triggered = math.floor(recent.depth * 2. / 3.)
      eek = "EEK! "..spl.." dB"
    end

    if triggered then
      triggered = triggered - 1
      if triggered == 0 then 
        triggered = false
        local data = recent:text()
        local pngfile = "startled-plot.png"
        plot.plot(data,
          pngfile,
          recent.depth/30., -- 1/3 trigger pos, 1/10 for time scale
          "Startled at "..isodate())
        local body = eek.."\n"..data
        print("email " .. addr, eek, isodate())
        mpack(addr, eek, pngfile)
      end
    end
    spl0 = spl
  end
end


>

>

>
|
>
>






>
>
>
>
>
|



















|
|
<
|


|
|







|
>



>
>
>
>
>
>
>
>
>
>
>
>
>






>
>
|
>
>
>
>
>
>
|









>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>















<

<
<
<
<
<
<
<
|


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

|
|
|
|
|
|
|

|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1
2
3
4
5
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
#! lua
local lanes = require "lanes".configure()
local plot = require "plotspl"
local struct = require "struct"

local rc = dofile"scared.rc"
rc.address = rc.address or "user@example.com"
rc.mode = rc.mode or "SPL"


-- today's date in (almost) ISO-8601 format
local function isodate() 
  return os.date"%Y-%m-%d %H:%M:%S" 
end

-- today's date for use in a file name
local function namedate() 
  return os.date"%Y%m%d-%H%M%S" 
end

-- make its argument be a single double-quoted string
function flatten(s)
  if type(s)=="string" then return '"'..s..'"' end
  if type(s)~="table" then return nil end
  return '"'..table.concat(s, '" "')..'"'
end

-- Send an email to addr with subject and body. If body
-- is nil then just repeat the subject. This assumes that
-- a "mail" command is available with the usual options.
local function email(addr, subj, body)
  body = body or subj
  local mail = ("mail -s '%q' %s"):format(subj,addr)
  local b = assert(io.popen(mail, "w"))
  b:write([[I heard a noise on ]], isodate(), "\r\n\r\n")
  b:write(body, "\r\n\r\n")
  b:write"(This an automated message.)\r\n"
  b:close()
end

-- Send a MIME packed email containing a file related
-- to the event to a recipient with a subject

local function mpack(address, subject, file)
  assert(address)
  assert(subject)
  assert(file)
  local cmd = ("mpack -a -s %q %q %s"):format(subject, file, address)
  print(cmd)
  os.execute(cmd)
end

-- Implement a buffer of samples. For simplicity, let the 
-- index increment without bound. Note that this will have
-- a problem after 2^53 samples, which is about 30 million 
-- years at 10 samples per second or 300 thousand years at
-- about 1000 samples per second.
local recent = {
  depth=200,
  n=1,

  -- clear the buffer and set a new depth
  clear=function(self,newdepth)
    newdepth = newdepth or 200
    local offset = self.n-self.depth-1
    for i=1,self.depth do
      self[i+offset] = nil
    end
    self.n = 1
    self.depth = newdepth
  end,

  -- add a sample to the buffer
  add=function(self,spl)
    local n = self.n
    self[n] = spl
    self[n-self.depth] = nil
    self.n = n + 1
  end,

  -- return the entire buffer as a raw blob
  raw=function(self)
    return table.concat(self,'',self.n-self.depth,self.n-1)
  end,

  -- return a CSV formatted buffer assuming the samples
  -- are SPL levels at 10 Hz
  CSVtext=function(self, header)
    local t = header and {"s, dB"} or {}
    local offset = self.n-self.depth-1
    for i=1,self.depth do
      if self[i+offset] then
        t[#t+1] = ("%0.1f, %s"):format(i/10,self[i+offset])
      end
    end
    return table.concat(t,"\n")
  end
}

local function auheader(buf, rate)
  local fmt = ">c4LLLLLL"
  local hdr = struct.pack(fmt,
      ".snd",                   -- 0 magic number
      struct.size(fmt),         -- 1 data offset
      #buf,                     -- 2 length or -1
      1,                        -- 3 encoding uLaw
      rate,                     -- 4 8152 Hz sample rate
      1,                        -- 5 mono
      0)                        -- 6 empty metadata string
  return hdr
end

local function writeau(au, buf)
  local f = assert(io.open(au,"wb"))
  f:write(auheader(buf, 8152), buf)
  f:close()
end

function writemp3(name, udata)
    local soxgate = 'compand .1,.2 -inf,-55.1,-inf,-55,-55 0 -90 .1 '
    local soxcompand = 'compand 0.3,1 6:-70,-60,-20 -5 -90 0.2 '
    local soxcmd = 'sox -t au - -t wav -b 16 - '
           .. soxgate
           .. soxcompand
           ..'| lame - '..name
    print(soxcmd)
    local fp = assert(io.popen('sh -c "'..soxcmd..'"', 'w'))
    fp:write(auheader(udata, 8000), udata)
    fp:close()
end

-- Quick and dirty way to force the UART to have the 
-- tty driver settings we need for clean raw access
-- to the port.
os.execute("stty -F /dev/ttyAMA0 115200"
	.." pass8 raw -iexten"
	.." -echo -echoe -echok -echoke -echoctl")

-- Open the UART for both reading and writing, and send
-- the SPLear command to go into raw SPL sample mode. The
-- output must be unbuffered because the current SPLear 
-- firmware would revert to 0.5 Hz summary mode if it
-- receives a newline.
local port = assert(io.open('/dev/ttyAMA0','r+'))
port:setvbuf"no"   -- no output buffer at all









local addr = flatten(rc.address)
print("Notifying "..addr)

-- command the requested mode
if rc.mode == "record" then
  port:write"R"      -- so this write is immediate
  local msgs = lanes.linda()

  -- temporary simulation of noises by 10 second timer
  lanes.timer(msgs, "tick", 10, 10)

  -- Function running in a separate thread that watches for loud
  -- noises signled via GPIO pin
  pinwatcher = lanes.gen("*", function()
      gpio = require "sysfsgpio"
      gpio.export(7)
      gpio.direction(7, "in")
      gpio.edge(7, "rising")

      print("Watching GPIO07 which is pin P1-26")
      while true do 
        --local _, t = msgs:receive"tick" -- external simulation timer
        local r = gpio.wait(7)
        if r == 0 then
          -- poll timed out
        elseif r == 1 then
	  msgs:send("eek", isodate())
        else
          -- something unexpected happened
        end
      end
    end
  )()

  -- main loop reads data as usual, keeping an eye out for
  -- being scared, after which it saves and emails a recording.
  local triggered = false
  local eek

  while true do
    local sample = port:read(800) -- read 1/10 sec audio 
    if sample then
      io.write("Rec "..(triggered and "TRG " or "    "), recent.n, "\r")
      io.flush()
      recent:add(sample)

      -- Notice GPIO pin
      local s = pinwatcher.status
      if not (s == "waiting" or s == "running" or s == "pending") then
	print("Watcher status: "..s)
	break
      end
      local eekflag, eekval = msgs:receive(0.0, "eek")
      if eekflag then
	if not triggered then
	  triggered = math.floor(recent.depth * 2. / 3.)
	  eek = "EEK! "..eekval
	end
      end
      if triggered then
	triggered = triggered - 1
	if triggered == 0 then 
	  triggered = false
	  local data = recent:raw(false)
	  local mp3file = "eek-"..namedate()..".mp3"
          writemp3(mp3file, data)
	  print("email " .. addr, mp3file, isodate())
	  mpack(addr, eek, mp3file)
	end
      end
      spl0 = spl
    end
  end

else -- mode ==  "SPL" 
  port:write"S"      -- so this write is immediate

  -- Loop reading the SPL samples and watching for loud
  -- noises, keeping a log of recent samples as we go.
  -- When a loud noise is heard, keep logging for a bit
  -- before sending email. Include the log in the email.
  local spl, spl0 
  local triggered = false
  local eek = ""


  while true do
    local line = port:read"*l" -- read a single line
    spl = tonumber(line)
    if spl then
      spl = 0.75 * spl
      io.write("SPL "..(triggered and "TRG " or "    "), spl, "\r")
      recent:add(spl)

      -- Watch for the trigger condition: 12 dB louder 
      if not triggered and spl0 and (spl - spl0 > 12.) then
	triggered = math.floor(recent.depth * 2. / 3.)
	eek = "EEK! "..spl.." dB"
      end

      if triggered then
	triggered = triggered - 1
	if triggered == 0 then 
	  triggered = false
	  local data = recent:CSVtext(false)
	  local pngfile = "tmp-SPL.png"
	  plot.plot(data,
	    pngfile,
	    recent.depth/30., -- 1/3 trigger pos, 1/10 for time scale
	    "Startled at "..isodate())
	  local body = eek.."\n"..data
	  print("email " .. addr, eek, isodate())
	  mpack(addr, eek, pngfile)
	end
      end
      spl0 = spl
    end
  end
end

Added Scared/sysfsgpio.lua.



































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
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
-- Module to make GPIO via the sysfs gpio class interface easy
-- for simple Lua applications.

-- simplest minimal module framework, just return M
local M = {
  _NAME=(...),
  _VERSION="v0.001",
}


-- Utility functions private to the module
local P = require "posix"

-- write str to the named file
function putfile(name,str)
  local f = assert(io.open(name,"w"))
  f:write(str)
  f:close()
end

-- read the named file and return its content
function getfile(name)
  local f = assert(io.open(name,"r"))
  local str f:read"*a"
  f:close()
  return str
end

-- transform a list into a set for quick lookup
function SET(t)
  local s = {}
  for _,v in ipairs(t) do
    s[v] = true
  end
  return s
end


-- container for GPIO functions, alias to M
local gpio=M
gpio.pins1_1 = SET{ -- Original RPi A, B
     0,  1,          4,          7,
     8,  9, 10, 11,         14, 15,
        17, 18,         21, 22, 23,
    24, 25,       }

gpio.pins1_2 = SET{ -- Rev 2 A, B
             2,  3,  4,          7,
     8,  9, 10, 11,         14, 15,
        17, 18,             22, 23,
    24, 25,     27}

gpio.pins2_0 = SET{ -- A+, B+, 2A, 2B
             2,  3,  4,  5,  6,  7,
     8,  9, 10, 11, 12, 13, 14, 15,
    16, 17, 18, 19, 20, 21, 22, 23,
    24, 25, 26, 27}

-- default to RPi B+ board
-- TODO: Read /proc/cpuinfo and get this right based on the
--       Hardware and Revision fields. For now, just assume
--       what I have will work.
gpio.pins = gpio.pins2_0

-- export a sysfs pin to userland
function gpio.export(pin)
  assert(gpio.pins[pin], "Can't use pin")
  putfile("/sys/class/gpio/export",tostring(pin))
end

-- return a sysfs pin to the kernel
function gpio.unexport(pin)
  assert(gpio.pins[pin], "Can't use pin")
  putfile("/sys/class/gpio/unexport",tostring(pin))
end

-- userland path to a specific pin's atribute file
function gpio.pinpath(pin,attr)
  return ("/sys/class/gpio/gpio%d/%s"):format(pin,attr)
end

-- set pin direction
function gpio.direction(pin,dir)
  local path = gpio.pinpath(pin, "direction")
  --print("Setting "..path.." to "..dir)
  putfile(path, dir)
end

-- set pin edge detector
function gpio.edge(pin,edge)
  local path = gpio.pinpath(pin, "edge")
  --print("Setting "..path.." to "..edge)
  putfile(path, edge)
end

-- blocking wait with timeout for an edge on a pin
function gpio.wait(pin)
  assert(gpio.pins[pin], "Can't use pin")
  local path = gpio.pinpath(pin, "value")
  if not gpio.pinfd then 
    gpio.pinfd = P.open(path, P.O_RDONLY)
  end
  local p = gpio.pinfd
  if not gpio.fds then gpio.fds = {} end
  if not gpio.fds[p] then gpio.fds[p] = {events={}, revents={}} end
  gpio.fds[p].events.PRI=true
  local ret = P.poll(gpio.fds, 10000)
  if ret == 0 then return 0 end
  P.lseek(p,0,P.SEEK_SET)
  ret = tonumber(P.read(p,8))
  --P.close(p)
  --gpio.pinfd = nil
  return ret
end 

--Example
--[===[

-- Use pin P1-28 aka GPIO7 for SPLear alerts
-- local gpio = require "sysfsgpio"
gpio.export(7)
gpio.direction(7,"in")
gpio.edge(7,"rising")

while(true) do
  local r = gpio.wait(7)
  if r == 0 then
    --print"timeout"
  elseif r==1 then
    local tim = P.gettimeofday()
    print("eek "..tim.sec..'.'..tim.usec)
  else
    print("???", r)
  end
end

if gpio.pinfd then P.close(gpio.pinfd) end

--]===]


-- TODO: Do some minimal validation that there actually is
--       a sysfs and error out if not.

return M

Added Scared/volley.lua.

































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
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
local lanes = require 'lanes'.configure()
local msg = lanes.linda()

local function volley(n,isa)
  print("Starting "..(isa and "A" or "B"), n)
  local rkey,skey = "a","b"
  if isa then 
    skey,rkey = rkey,skey
  end
  msg:send("done",true)
  for i=1,n do 
    local ball,value = msg:receive(rkey)
    msg:send(skey, value+1)
  end
  msg:send("done",true)
end

print("starting threads")
local gen = lanes.gen("",volley)
local N = 1e5
local a = gen(N, true)
local b = gen(N)
local ok,_ = msg:receive(0.5, msg.batched, "done", 2)
if not ok then print "ouch" return end
local t0 = os.time()
msg:send("a", 0)
msg:receive(nil, msg.batched, "done", 2)
local t1 = os.time()

print((t1-t0).." sec")
print(((t1-t0)/N).." sec")
print((N/(t1-t0)).." per sec")