Merge remote-tracking branch 'upstream/master'
authorCallum <callum@callum-ubuntu.(none)>
Sun, 20 Oct 2013 08:00:44 +0000 (16:00 +0800)
committerCallum <callum@callum-ubuntu.(none)>
Sun, 20 Oct 2013 08:00:44 +0000 (16:00 +0800)
41 files changed:
irc/log
server/Makefile
server/actuator.c
server/actuator.h
server/actuators/filetest.c
server/bbb_pin_defines.h
server/common.h
server/control.c
server/control.h
server/data.c
server/fastcgi.c
server/fastcgi.h
server/image.c
server/image.h
server/login.c
server/main.c
server/options.h
server/parameters
server/sensor.c
server/sensor.h
server/sensors/Makefile
server/sensors/pressure.c [new file with mode: 0644]
server/sensors/pressure.h [new file with mode: 0644]
server/sensors/resource.c
server/sensors/strain.c
server/shadow [new file with mode: 0644]
testing/MCTXWeb/public_html/control.html
testing/MCTXWeb/public_html/data-Justin.html [deleted file]
testing/MCTXWeb/public_html/data.html [new file with mode: 0644]
testing/MCTXWeb/public_html/graph.html
testing/MCTXWeb/public_html/help-Justin.html [deleted file]
testing/MCTXWeb/public_html/help.html [new file with mode: 0644]
testing/MCTXWeb/public_html/index.html
testing/MCTXWeb/public_html/pintest.html
testing/MCTXWeb/public_html/static/OpenSansBold.ttf [new file with mode: 0644]
testing/MCTXWeb/public_html/static/base64.js [deleted file]
testing/MCTXWeb/public_html/static/mctx.control.js [new file with mode: 0644]
testing/MCTXWeb/public_html/static/mctx.graph.js
testing/MCTXWeb/public_html/static/mctx.gui.js
testing/MCTXWeb/public_html/static/style.css
testing/pygui/pygui.py [new file with mode: 0755]

diff --git a/irc/log b/irc/log
index 3b03edb..d799f2d 100644 (file)
--- a/irc/log
+++ b/irc/log
 23:29 < jtanx> too tired to check, but I refactored it and I think it still works
 23:30 < jtanx> I'll just leave it in my fork for now
 23:30 -!- jtanx [[email protected]] has quit ["bye"]
+--- Day changed Mon Oct 14 2013
+10:03 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+10:03 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+19:15 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+19:19 < jtanx> this is stupi
+19:19 < jtanx> d
+19:19 < jtanx> flot is still the easiest to use
+19:20 < jtanx> chart.js can only plot a line chart, so all the data series must share the same x-axis points
+19:20 < jtanx> jqplot sucks
+19:21 < jtanx> oh well, I'll work on this some other time
+19:21 < jtanx> stick with flot for now
+19:40 < sam_moore> Re actuators: No, Fatal is probably not the correct response to an invalid user input :P
+19:40 < sam_moore> Re just using flot: OK
+19:43 < sam_moore> I think we need to do a grep -R "Fatal" and get rid of the ones that don't actually need to be Fatal
+19:43 < sam_moore> Which is probably most of them
+19:44 < sam_moore> Working on more useful user based control now
+19:45 < jtanx> ok
+19:46 < jtanx> I wonder if it's possible to selectively colour text in a textarea with javascript
+19:47 < sam_moore> Probably?
+19:47 < jtanx> I have a feeling that you'd have to surround those ones in <p> blocks
+19:48 < sam_moore> Hmm, but that would make them all on new paragraphs
+19:48 < jtanx> fine, <span>s
+19:48 < sam_moore> That makes sense
+19:48 < jtanx> but you'd probably need to surround highlighted text in some sort of <> block
+19:48 < jtanx> so you can style it
+19:48 < sam_moore> Makes sense
+19:48 < sam_moore> What are you going to colourise?
+19:49 < jtanx> say if there's a fatal message
+19:49 < jtanx> you colour it red?
+19:49 < jtanx> make it blink, 90s style
+19:52 < sam_moore> Hahaha
+19:52 < sam_moore> Sounds good
+19:53 < sam_moore> Colourising the labels for the graph might be useful too
+19:53 < sam_moore> (Possibly easier than adding labels to the flot plot?)
+19:53 < jtanx> hmm
+19:54 < jtanx> what I was thinking of doing was to use nvd3, which generates the graphs using the svg element instead of a canvas
+19:54 < jtanx> then you can use something like http://code.google.com/p/canvg/ to render the svg in js to a png file
+19:55 < sam_moore> Interesting
+19:55 < jtanx> no idea if it will work
+19:56 < sam_moore> Do you even have to render it to png? SVG is a good format for graphs
+19:56 < jtanx> i mean if you want to save it
+19:56 < jtanx> i dunno, most users will go
+19:56 < jtanx> what's this svg format
+19:56 < sam_moore> That's what I mean; can you save it as an svg?
+19:56 < sam_moore> Hahaha
+19:56 < jtanx> probably
+19:56 < sam_moore> If it were me, I would *want* the svg format
+19:56 < sam_moore> You can edit the graph in inkscape
+19:57 < sam_moore> Really easily
+19:57 < jtanx> yeah
+19:57 < jtanx> I'd prefer the svg format too
+19:57 < sam_moore> Get SVG working and if you really want, add a radio box to choose the file format
+19:57 < jtanx> yep
+19:58 < sam_moore> I thought FCGI_LockControl was being called on any user action, but it looks like it's only called in login.c now
+19:58 < sam_moore> Is that the change you were talking about earlier?
+19:59 < jtanx> nah
+19:59 < jtanx> fcgi_lockcontrol should only be called in login.c
+19:59 < jtanx> there was this leftover part in control.c
+19:59 < sam_moore> Oh, right, nevermind
+19:59 < jtanx> that called it too
+20:00 < sam_moore> I was thinking of FCGI_HasControl,that's ok
+20:00 < jtanx> yeah
+20:10 < MctxBot> .
+20:12 < sam_moore> ?
+20:12 < sam_moore> MctxBot: Have you become sentient yet?
+20:12 < jtanx> I was just testing notification on all messages for chatzilla
+20:14 < sam_moore> Ok
+20:53 < sam_moore> I think the correct way to do this is to store more stuff in the FCGI_Context
+20:54 < sam_moore> Like the name of the experiment currently being conducted
+20:54 < jtanx> hmm
+20:54 < sam_moore> I noticed that control.c avoids storing the actual name of the experiment anywhere
+20:54 < sam_moore> But it's late and I'm just going to hack something together
+20:55 < jtanx> yeah
+20:55 < jtanx> I don't particularly like how 'experiments' were handled in control.c
+20:55 < sam_moore> g_control stores the user name of whoever last succeeded in doing an "action=start"
+20:55 < sam_moore> I can rework it, but not tonight
+20:55 < jtanx> that's fine
+21:41 < sam_moore> ==21160==  Uninitialised value was created by a stack allocation
+21:41 < sam_moore> ==21160==    at 0x6A22DBE: __md5_crypt_r (md5-crypt.c:121)
+21:41 < sam_moore> :S
+21:41 < jtanx> .....
+21:41 < sam_moore> Not causing an error, but still
+21:41 < jtanx> what does that even mean
+21:42 < sam_moore> It's nice to know that the linux crypt functions that are used for parsing /etc/shadow have uninitialised values
+21:42 < jtanx> you sure you're calling it correctly
+21:42 < jtanx> ?
+21:42 < sam_moore> The full text (brace for impact)
+21:42 < sam_moore> ==21160== Conditional jump or move depends on uninitialised value(s)
+21:42 < sam_moore> ==21160==    at 0x6D75188: ??? (strcpy.S:98)
+21:42 < sam_moore> ==21160==    by 0x6A230E7: __md5_crypt_r (md5-crypt.c:249)
+21:42 < sam_moore> ==21160==    by 0x40A5F6: Login_Shadow (login.c:93)
+21:42 < sam_moore> ==21160==    by 0x40AA7B: Login_Handler (login.c:243)
+21:42 < sam_moore> ==21160==    by 0x404CE3: FCGI_RequestLoop (fastcgi.c:548)
+21:42 < sam_moore> ==21160==    by 0x4052AE: main (main.c:143)
+21:42 < sam_moore> ==21160==  Uninitialised value was created by a stack allocation
+21:42 < sam_moore> ==21160==    at 0x6A22DBE: __md5_crypt_r (md5-crypt.c:121)
+21:42 < sam_moore> if (strcmp(crypt(pass, salt), buffer+passwd_index) == 0)
+21:43 < sam_moore> Looks like the correct usage to me
+21:43 < sam_moore> I mean, it's *working*
+21:43 < jtanx> ~.~
+21:43 < jtanx> well then
+21:44 < sam_moore> It's probably someone being lazy with initialising things
+21:44 < jtanx> https://bugzilla.redhat.com/show_bug.cgi?id=760262
+21:44 < sam_moore> GCC under linux usually initialises things to zero
+21:44 < jtanx> https://bugzilla.redhat.com/show_bug.cgi?format=multiple&id=699917
+21:44 < sam_moore> But under other things (like Mac OSX) it doesn't; if it's not initialised you can't assume anything
+21:45 < jtanx> I thought C standard was any global variables were zeroed
+21:45 < jtanx> anything else is undefined
+21:45 < sam_moore> Well it's probably not a global variable then
+21:45 < sam_moore> But I think you're right
+21:45 < sam_moore> I just remember in CITS1210
+21:45 < jtanx> from the bug report - 'This is an annoying, but harmless false positive warning.'
+21:46 < sam_moore> We had a bunch of variables that weren't initialised
+21:46 < sam_moore> gcc under debian/ubuntu was zeroing them for us
+21:46 < sam_moore> So things still worked
+21:46 < jtanx> yeah
+21:46 < sam_moore> Go to the Mac lab...
+21:46 < sam_moore> Everything exploded
+21:46 < jtanx> hahahaha
+21:46 < jtanx> ah... CITS1210
+21:46 < jtanx> when did you take it?
+21:46 < sam_moore> Aaages ago
+21:46 < sam_moore> 2009?
+21:47 < jtanx> that was long ago
+21:47 < sam_moore> Yes
+21:47 < jtanx> 2011 for me
+21:47 < sam_moore> We got 100% though :)
+21:47 < sam_moore> For the second project
+21:47 < jtanx> sweet
+21:47 < jtanx> I think I got that too
+21:47 < jtanx> they docked marks off the first one for some stupid reason 
+21:47 < sam_moore> (He had an infinite loop in the sample solution, which we found and eliminated in our solution, I think that was the main reason)
+21:48 < jtanx> ook
+21:48 < sam_moore> (He probably didn't appreciate the #ifdef BE_REALLY_STUPID // Replicate the behaviour *exactly* as requested by the lecturer)
+21:48 < jtanx> :P
+21:48 < sam_moore> Good fun
+21:49 < jtanx> yeah, one of the comments on mine was 'did not exactly match whitespace of output'
+21:49 < sam_moore> Haha, it was pretty harsh with the replicating exactly
+21:49 < sam_moore> We had a long debate about whether we should replicate the infinite loop or not
+21:49 < sam_moore> I think we actually submitted it with "BE_REALLY_STUPID" defined :S
+21:50 < jtanx> hahahaha
+22:24 < jtanx> now to look into nvd3
+22:36 < sam_moore> I've pushed some progress, I will try and clean it up later this week (it's pretty hacky)
+22:36 < jtanx> cool
+22:37 < sam_moore> I do like the idea of having svg plots, if it works it will be much nicer than flot
+22:37 < jtanx> hopefully
+22:37 < jtanx> it doesn't work in ie8 without this compat library
+22:37 < sam_moore> Ah
+22:37 < jtanx> and with compat it'll probably be slow
+22:37 < jtanx> but oh well
+22:38 < sam_moore> Fallback to flot if browser is ie8? :P
+22:38 < jtanx> yeah
+22:38 < jtanx> maybe
+22:38 < jtanx> that could work
+22:39 < sam_moore> I'm pretty sure it's within our rights to say "You need to use one of these browsers" since it's so easy to install browsers
+22:39 < jtanx> true
+22:39 < jtanx> ie8 is more an afterthought anyway
+22:39 < sam_moore> But I suppose if they have some stupid SOE that only includes ie6...
+22:39 < jtanx> if you use ie6 you'd be screwed anyway
+22:39 < sam_moore> Haha
+22:40 < sam_moore> The first flot page I made didn't work in my phone's browser, but that's probably a JavaScript setTimeout thing
+22:40 < jtanx> do browsers even support the canvas element
+22:40 < jtanx> *mobile browsers
+22:41 < sam_moore> Well it showed the first set of data but didn't update
+22:41 < jtanx> oh 
+22:41 < jtanx> right
+22:41 < sam_moore> Don't worry, we don't need to support phone browsers
+22:41 < sam_moore> Or at least, until we tell Adrian that we don't support phone browsers, we won't have to support phone browsers :P
+22:42 < jtanx> hehehe
+23:12 -!- jtanx [[email protected]] has quit [Connection reset by peer]
+23:13 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+23:20 < jtanx> hahaha nvd3 crashed firefox
+23:20 < jtanx> let me try again
+23:20 < jtanx> if I disconnect, you know why
+23:22 < jtanx> well it didn't crash, but it didn't work
+23:22 < jtanx> oh well, I'll try again tomorrow
+23:22 -!- jtanx [[email protected]] has quit ["bye"]
+--- Day changed Tue Oct 15 2013
+12:08 < sam_moore> http://jayrambhia.com/blog/capture-v4l2/
+12:08 < sam_moore> Might be useful?
+12:08 < sam_moore> I suspect the issue with the cameras is related to the colour format
+12:08 < sam_moore> MJPEG fails, but YUVU (whatever the hell that is) succeeds
+12:09 < sam_moore> Although it is still only giving 320x240
+12:13 < sam_moore> I noticed uvccapture, etc are quite buggy on my own debian laptop
+12:13 < sam_moore> I'm hoping that it might actually be a shitty userspace error that we can fix by using the v4l2 API directly
+12:13 < sam_moore> Although it's not likely
+12:14 < sam_moore> May as well try
+12:14 < sam_moore> But uvccapture just likes to hang forever
+12:14 < sam_moore> If you give it larger resolutions
+18:52 -!- james__ [[email protected]] has joined #mctxuwa_softdev
+19:29 -!- james__ [[email protected]] has quit [Ping timeout]
+21:27 -!- James__ [[email protected]] has joined #mctxuwa_softdev
+21:31 < James__> Hey Sam
+21:39 < James__> Having Issues uploading to the GUI to git but i have successfully tested it on Firefox, Chrome and IE.  I will continue trying to upload but if i can't i will see if i can find you or Jeremy tomorrow 
+21:57 -!- James__ [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+--- Day changed Wed Oct 16 2013
+13:39 -!- MctxBot [[email protected]] has quit [Ping timeout]
+13:59 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+13:59 < jtanx> ...
+13:59 < jtanx> I brought the bbb home
+14:00 < jtanx> booted it off an ubuntu image, and uvccapture can take 800x600 images fine from my webcap
+14:00 < jtanx> webcam*
+14:00 < jtanx> I don't know if it's because it's a different camera, or because it's a different os
+14:34 < jtanx> I just tested with the api/image and I can get 800x600 fine
+14:34 < jtanx> too
+14:38 < jtanx> I can even do 1600x1200 images just fine 
+14:38 < jtanx> ................
+14:38 < jtanx> ._.
+14:38 < jtanx> I'll be bringing this in tomorrow, see if it works with the cameras that we're going to be using
+14:39 < jtanx> Since my 1gb sd card was not big enough to opencv, I've actually flashed the internal memory with ubuntu
+14:39 < jtanx> (so I could install opencv)
+14:41 < jtanx> wowthat is so stupid
+14:42 < jtanx> so either it's the cameras that we bought, or the os 
+14:42 < jtanx> but it works at 1600x1200
+14:42 < jtanx> (max resolution of my webcam)
+14:43 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+15:00 < jtanx> ok, I just tried it with the os we've been running before (on your 32gb sd card), and it also works at 1600x1200
+15:00 < jtanx> so it's an issue with the camera
+15:01 < jtanx> (e.g selecting a camera that actually works with the BBB
+15:14 -!- jtanx [[email protected]] has quit ["brb"]
+15:16 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+20:10 -!- MctxBot [[email protected]] has quit [Ping timeout]
+21:41 -!- jtanx_ [[email protected]] has joined #mctxuwa_softdev
+21:56 -!- jtanx [[email protected]] has quit [Ping timeout]
+23:28 -!- jtanx_ [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+23:28 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+--- Day changed Thu Oct 17 2013
+09:16 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+09:17 < jtanx> ....
+09:17 < jtanx> I just ran the bBB with ubuntu and used the usb microscope
+09:17 < jtanx> and it worked
+09:18 < jtanx> and so too does it work on the original os
+09:18 < jtanx> wtf
+09:33 < jtanx> hahahaa 
+09:33 < jtanx> it crashed because it ran out of memory 
+09:33 < jtanx> I tried having both cameras at once
+09:33 < jtanx> and switching between them 
+09:34 < jtanx> but other than that it works
+09:34 < jtanx> this is weird
+09:44 < jtanx> about 1/5 tries fails at 1600x1200 with 'select timeout'
+09:44 < jtanx> probably because it can't buffer that fast or something
+09:45 < jtanx> but definitely to run both cameras at once (or at least have them connected), you require an external power source, either for the beaglebone or for the usb hub
+09:47 < jtanx> yep
+09:47 < jtanx> if you do
+09:47 < jtanx> modprobe uvcvideo nodrop=1
+09:47 < jtanx> It will only display part of the image
+09:47 < jtanx> probably because it hasn't filled the buffer yet
+09:48 < jtanx> but if you load uvcvideo with normal settings
+09:48 < jtanx> it either displays the full image, or no image at all
+09:48 < jtanx> the problem is, I don't know how you detect this in opencv
+10:10 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+13:47 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+14:55 -!- MctxBot [[email protected]] has quit [Ping timeout]
+17:14 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+17:20 -!- MctxBot [[email protected]] has quit ["leaving"]
+17:22 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+22:38 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+--- Log closed Fri Oct 18 11:45:55 2013
+--- Log opened Fri Oct 18 11:46:19 2013
+11:46 -!- sam_moor1 [[email protected]] has joined #mctxuwa_softdev
+11:46 -!- Irssi: #mctxuwa_softdev: Total of 3 nicks [0 ops, 0 halfops, 0 voices, 3 normal]
+11:46 -!- Irssi: Join to #mctxuwa_softdev was synced in 8 secs
+11:56 -!- sam_moore [[email protected]] has quit [Ping timeout]
+16:03 -!- You're now known as sam_moore
+16:03 -!- Irssi: #mctxuwa_softdev: Total of 2 nicks [0 ops, 0 halfops, 0 voices, 2 normal]
+16:03 < sam_moore> MctxBot: Tell your master that snoopy is broken
+17:41 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+17:42 < jtanx> woops
+17:42 < jtanx> I probably screwed up fstab on the sd card when trying to enable swap space
+17:42 < jtanx> sorry about not replying earlier
+19:42 < sam_moore> Haha
+19:42 < jtanx> ~.~
+19:42 < sam_moore> We used ubuntu to test the pressure sensor and microphone
+19:42 < jtanx> what were you doing with the BBB?
+19:42 < jtanx> oh 
+19:42 < jtanx> how did that go?
+19:43 < sam_moore> Alright, flot and/or JavaScript suck at large numbers of points though
+19:43 < jtanx> yeah
+19:43 < jtanx> did you finally get your hands on a voltage divider or something
+19:44 < jtanx> or hsa the electronics team finished their stuff
+19:44 < sam_moore> We just made one, but electronics does have one
+19:45 < sam_moore> "We have 2 boards and 2 of them don't work"
+19:45 < sam_moore> GPIO can't trigger the relays it would seem
+19:45 < jtanx> well
+19:46 < jtanx> what are they going to do about that
+19:46 < sam_moore> Although we only tested 2 outputs, apparently some are 4mA some are 6mA and the thing needs 5mA
+19:47 < sam_moore> I don't know? Try every single pin, actually measure the current drawn by the relay switch
+19:47 < sam_moore> Replace the transistors on the relay
+19:47 < jtanx> haha
+19:47 < jtanx> that sounds so dodge
+19:47 < sam_moore> We tried 2 GPIO pins at once :S
+19:47 < jtanx> in parallel?
+19:47 < sam_moore> Didn't work
+19:48 < sam_moore> Yeah
+19:48 < jtanx> hahaha
+19:48 < sam_moore> But I think that might cause one to draw current from the other
+19:48 < jtanx> hmm
+19:49 < sam_moore> Need to actually look at the GPIO circuit diagram to work out what that would do
+19:49 < sam_moore> Our software can sample more than fast enough for the microphone
+19:50 < jtanx> that's cool
+19:50 < jtanx> could you see it on the graph
+19:50 < sam_moore> But plotting it in flot crashes the browser
+19:50 < sam_moore> Sort of
+19:50 < sam_moore> Had to change start_time to get all the points
+19:51 < jtanx> Flot doesn't sound too reliable
+19:51 < sam_moore> It crashes after ~1s
+19:51 < sam_moore> min
+19:51 < sam_moore> not second
+19:51 < jtanx> But can we just cut down on the number of data points
+19:51 < jtanx> how many datapoints is that after 1 min
+19:51 < sam_moore> It's obviously incredibly inefficient
+19:52 < sam_moore> I will see if averaging points helps flot
+19:52 < sam_moore> Or if it's actually JavaScript not liking really big arrays
+19:52 < jtanx> yeah
+19:52 < jtanx> I think having large arrays consumes an enormous amount of memory in js, for some reason
+19:53 < sam_moore> Dammit
+19:54 < sam_moore> We could make a python GUI using requests and pyplot :P
+19:54 < jtanx> -.-
+19:54 < jtanx> wouldn't it be worse, trying to plot it on the beaglebone
+19:55 < jtanx> given that you're using a full blown computer to plot it
+19:55 < sam_moore> No, you still plot it on the client
+19:55 < jtanx> How does that work
+19:56 < jtanx> You'd have to render it on the BBB, no?
+19:56 < sam_moore> Nope; get data the same way through HTTP and plot on the client
+19:56 < jtanx> what's the difference?
+19:57 < sam_moore> The BBB doesn't gave a problem transferring data points
+19:58 < sam_moore> The browser ie JavaScript sucks at coping with them
+19:58 < jtanx> Yeah, but if you say plot on the client, how do you plot on the client?
+19:58 < jtanx> ohhhhhhh
+19:58 < jtanx> Right I understand now
+19:59 < sam_moore> Python has modules for plotting
+19:59 < sam_moore> And modules for HTTP requests
+19:59 < jtanx> You're saying instead of a web interface a python gui instead
+19:59 < sam_moore> Maybe
+19:59 < jtanx> ??
+19:59 < sam_moore> I can at least do a proof of concept
+19:59 < jtanx> I'm lost 
+20:00 < sam_moore> But we probably shouldn't redesign the entire GUI...
+20:00 < jtanx> Hey, I'm all for it if it can be done
+20:20 < jtanx> Now, to do this control page
+20:20 < jtanx> A username can only contain alphanumeric characters
+20:21 < jtanx> right?
+20:21 < sam_moore> Yep
+20:21 < jtanx> okay
+20:21 < jtanx> was just thinking if someone had say .. in their username...
+20:25 < sam_moore> You can allow other characters
+20:25 < jtanx> well as in
+20:25 < sam_moore> Not '=' though
+20:25 < jtanx> having .. in their username would move you up a directory
+20:26 < sam_moore> Ah, cunning
+20:26 < sam_moore> Thwarted by general paranoia though
+20:26 < jtanx> :P
+20:26 < sam_moore> No stupid characters in usernames
+20:27 < sam_moore> Definitely no '/' allowed
+20:27 < jtanx> hahaha
+20:27 < jtanx> I think an alphanumeric limitation is good
+20:27 < jtanx> until the university stipulates having random characters in usernames
+20:28 < sam_moore> You forget that if the university does that they will have to cope with the same kind of bullshit
+20:29 < jtanx> Yeah, true that
+21:42 < sam_moore> I pushed a python plotting thing, but I don't really think we should be switching to it at this stage.
+21:42 < jtanx> Okay
+21:42 < sam_moore> I'll try tweak the flot GUI tomorrow to make it less shit
+21:43 < jtanx> hahaha
+21:58 < jtanx> urgh
+21:58 < jtanx> barely started on the control page
+21:59 < jtanx> I guess I'll have to complete it tomorrow
+21:59 < jtanx> even though we're probably (maybe) going with james gui
+22:00 < jtanx> I've removed the width restriction 
+22:00 < jtanx> and you can hide the navigation bar
+22:00 < jtanx> for more space
+22:11 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+--- Day changed Sat Oct 19 2013
+07:59 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+10:39 < jtanx> blegh, now to start on the js for the control page
+11:10 < jtanx> what would you say if instead of all this chdir business
+11:10 < jtanx> one of the initial options to start the software is to specify where you want experiments to be held
+11:11 < jtanx> e.g /usr/share/experiments or whatever
+11:11 < jtanx> then you build the path based on the username
+11:25 < sam_moore> That sounds good
+11:26 < jtanx> Okay, I'll try to do that then
+14:37 < sam_moore> There's some kind of error in mctx.gui.js now
+14:37 < jtanx> what's happening?
+14:37 < sam_moore> A syntax error
+14:37 < jtanx> let me check
+14:38 < jtanx> what page is this on?
+14:38 < sam_moore> Line 272 (mctx.gui.js): outdiv[0].scrollHeight - outdiv.height()
+14:38 < sam_moore> Caused by loading graph.html
+14:38 < jtanx> ahhhh
+14:38 < jtanx> I know why
+14:38 < jtanx> maybe
+14:39 < jtanx> I decided to just to $("#errorlog").seterrorlog in mctx.gui.js
+14:39 < jtanx> so I don't have to repeat that on every page
+14:39 < sam_moore> The debug function also causes some error in chromium
+14:39 < jtanx> but if the page deosn't have an error log
+14:39 < jtanx> hahaha
+14:39 < jtanx> chromium
+14:39 < jtanx> what does it say
+14:39 < sam_moore> Didn't I tell you that something was causing iceweasel to segfault? I have no choice! :(
+14:40 < jtanx> :P
+14:40 < sam_moore> console.log.apply(this, arguments); causes an uncaught type error: illegal invocation
+14:40 < sam_moore> I shall replace it with "alert"
+14:40 < jtanx> hmm
+14:41 < jtanx> it's because chrome has a different implementation of console.log
+14:41 < jtanx> http://stackoverflow.com/questions/9521921/why-does-console-log-apply-throw-an-illegal-invocation-error
+14:41 < jtanx> for that bug
+14:41 < sam_moore> Right
+14:42 < sam_moore> Why is the call to outdiv.scrollTop on line 272 spread over 3 lines?
+14:42 < sam_moore> ... That's probably why it syntax errors
+14:42 < jtanx> nah
+14:42 < jtanx> it's legit
+14:43 < jtanx> the problem is there's no div there if you don't define #errorlog somewhere in your html
+14:43 < sam_moore> I thought the html was supposed to load any elements specific to that page
+14:44 < sam_moore> Which should avoid this sort of thing happening
+14:44 < jtanx> Yeah, except that we were repeating the same sort of load code
+14:44 < jtanx> in most of the pages
+14:44 < jtanx> I've just added a check in seterrorlog
+14:44 < jtanx> which should ignore if there's no such div anyway
+14:44 < sam_moore> ok
+14:44 < jtanx> that whole 'experiment dir' thing is ~.~
+14:44 < jtanx> I think it's mostly done
+14:45 < sam_moore> Ok
+14:45 < jtanx> so
+14:45 < jtanx> instead of having a file 'name.exp'
+14:45 < sam_moore> Yeah that was hacky
+14:45 < jtanx> it's a folder 'name.exp'
+14:45 < jtanx> all the stuff for one experiment gets stuck in that folder
+14:45 < sam_moore> That's slightly less hacky
+14:45 < jtanx> so you have something like
+14:45 < sam_moore> Makes sense
+14:46 < jtanx>  /experiments_folder/username/experiment_name.exp
+14:46 < sam_moore> Cool
+14:46 < sam_moore> If it ever is changed to use local user accounts you can just set experiments_folder to /home
+14:47 < jtanx> yeah
+14:47 < jtanx> experiments_folder is just an argument you pass to it when you start it
+14:47 < jtanx> -e experiment_folder
+14:49 < sam_moore> Found a syntax error in graph.html now
+14:49 < jtanx> just pushing stuff now
+14:49 < jtanx> but what's the errro?
+14:50 < sam_moore> There's an unterminated bracket on the runBeforeLoad.done
+14:50 < sam_moore> I think
+14:50 < jtanx> ah
+14:50 < sam_moore> So many brackets
+14:50 < jtanx> hehehe
+14:50 < jtanx> yeah you're probably right
+14:50 < jtanx> netbeans is giving angry red on the bracket
+14:51 < jtanx> do you want me to push the fix for that?
+14:51 < sam_moore> Well there are other issues with the page
+14:52 < sam_moore> No method "always" where it's being chained on the document.ready
+14:53 < jtanx> no wait
+14:53 < sam_moore> Wait what the hell is it meant to do there
+14:53 < jtanx> [14:52:32.186] ReferenceError: mctx is not defined @ http://localhost:8383/MCTXWeb/static/mctx.graph.js:8
+14:53 < jtanx> hmm
+14:53 < jtanx> I broke something 
+14:53 < sam_moore> That's not the problem I'm getting, but generally there are lots of wierd things here
+14:53 < jtanx> yeah
+14:53 < jtanx> sorry
+14:53 < jtanx> it was probably working before I pushed changes yesterday
+14:54 < sam_moore> You have runBeforeLoad.done() calling $(document).ready which does nothing, but is chained to .always which will then call $(document).ready which then loads $("#graph-controls").setDevices()
+14:55 < sam_moore> ... I think what I originally had was runBeforeLoad.always() calling $("#graph-controls").setDevices
+14:55 < sam_moore> Possibly inside a document.ready
+14:55 < sam_moore> But now mctx.gui.js is calling document.ready itself...
+14:55 < jtanx> waiit
+14:55 < jtanx> I think it's ok now
+14:56 < jtanx> it was just brackets
+14:56 < jtanx> I'll try it on my server first
+14:58 < jtanx> yeah, I see what you mean
+15:01 < jtanx> ahh
+15:01 < sam_moore> Well I fixed it enough to test stuff
+15:01 < jtanx> yeah
+15:02 < jtanx> you planning on modifying stuff?
+15:02 < sam_moore> I think it's just a matter of having runBeforeLoad().done() call $(document).ready() and then initialise stuff
+15:02 < jtanx> I'll just keep it in my repo for now
+15:03 < sam_moore> With runBeforeLoad.fail if you want to handle bad stuff
+15:03 < jtanx> I just made it
+15:03 < jtanx> runBeforeLoad().always(function() {
+15:03 < jtanx>         $(document).ready(function() {
+15:03 < jtanx>           $("#graph-controls").setDevices();
+15:03 < jtanx>         });
+15:03 < jtanx>       });
+15:03 < sam_moore> Yep, that's what I just did
+15:03 < sam_moore> You can commit it and I'll pull it
+15:03 < jtanx> okay
+15:03 < sam_moore> If you fix the console and the errorlog things as well
+15:03 < jtanx> yep
+15:03 < jtanx> pushed that too
+15:05 < sam_moore> Hmm, start_time = -1 is not actually giving the most recent second of data
+15:05 < sam_moore> That might be a server API problem
+15:05 < jtanx> hmmm
+15:05 < sam_moore> But it might also explain why things were shitting themselves so much with the fast sampling rate
+15:06 < jtanx> what is it giving?
+15:06 < sam_moore> Because the jQuery would be getting hundreds of thousands of points...
+15:07 < sam_moore> At the moment the server API is not giving any points and giving back a stupid value for start_time
+15:07 < sam_moore> I think I might have broken it when I changed all the clocks
+15:07 < jtanx> when did you change ti?
+15:07 < sam_moore> Just now
+15:07 < jtanx> ah
+15:07 < sam_moore> I'm hoping that my first assumption about the thousands of points was the case before I broke it as well :P
+15:08 < sam_moore> (ie: It was already broken before I broke it more)
+15:08 < jtanx> ahahahah
+15:08 < jtanx> I can try
+15:08 < jtanx> I still have the old version
+15:09 < jtanx> with start_time=-1 it returns nothing
+15:09 < jtanx> start_time in the response is constant
+15:11 < sam_moore> Yeah I get wierd stuff
+15:11 < sam_moore> start_time = 17889.590701
+15:12 < sam_moore> "current_time" : 18589.539255
+15:12 < jtanx> is that with clock_gettime?
+15:12 < sam_moore> "running_time" : 699.948554,
+15:12 < sam_moore> Yes
+15:13 < sam_moore> Oh herdurp
+15:13 < sam_moore> start_time in the JSON response is *not* the same as start_time the parameter to the sensor
+15:13 < jtanx> yeah
+15:13 < sam_moore> .... should probably change that
+15:13 < jtanx> start time of experiment
+15:14 < jtanx> experiment_start_time?
+15:14 < jtanx> long variable names...
+15:14 < sam_moore> I think it's actually the program start time
+15:14 < jtanx> nup
+15:14 < jtanx> experiment starttime
+15:14 < sam_moore> Alright
+15:14 < jtanx> oh
+15:14 < jtanx> actually you may be right
+15:14 < sam_moore> Haha
+15:14 < jtanx> I thought i changed it
+15:14 < jtanx> wway back when i did the control stuff
+15:15 < jtanx> initially
+15:15 < sam_moore> ControlData has a start_time variable
+15:15 < jtanx> yeah
+15:15 < sam_moore> But g_options has a start_time variable
+15:15 < sam_moore> -_-
+15:15 < jtanx> that's confusing
+15:15 < jtanx> hahaha
+15:15 < sam_moore> Yeah, what idiot designed this...
+15:15 < jtanx> ~.~`
+15:19 < sam_moore> Right, sensor values are being saved relative to Control_GetStartTime (experiment starting time)
+15:19 < sam_moore> That's fine
+15:19 < jtanx> yep
+15:19 < sam_moore> Sensor_Handler gets "current_time" from that as well
+15:19 < jtanx> Thread safety on that is dodgey
+15:19 < jtanx> I must say
+15:20 < jtanx> but it's probably fine
+15:20 < sam_moore> It's fine, clock_gettime is posix thread safe
+15:21 < sam_moore> Oh, do you mean if the experiment changes
+15:21 < jtanx> yeah
+15:21 < jtanx> the whole *Control_GetStartTime() looks dodgy too
+15:22 < sam_moore> Perhaps just a "double Control_CurrentTime()" would be better
+15:22 < sam_moore> But it works
+15:22 < jtanx> hmm
+15:24 < sam_moore> Anyway, I think start_time=-1 is not giving any data because the default sampling rate is 1s and therefore current_time - 1 is outside the data range most of the time
+15:25 < sam_moore> start_time=-3 gives a single point with the default sampling rate
+15:25 < sam_moore> Increasing the sampling rate then you appear to get the right points
+15:31 < sam_moore> I'm going to repeat my timestamp test with the current software under regular kernel and RT linux
+15:32 < sam_moore> I know we can't ever get RT linux but I want to see if it makes much of a difference
+15:46 < jtanx> okay
+15:50 < jtanx> finally... back to actually coding the control page
+22:59 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
index c5be05f..39a71fb 100644 (file)
@@ -1,6 +1,6 @@
 # Makefile for server software
 CXX = gcc
-FLAGS = -std=c99 -Wall -pedantic -g -I/usr/include/opencv -I/usr/include/opencv2/highgui -L/usr/lib
+FLAGS = -std=gnu99 -Wall -pedantic -g -I/usr/include/opencv -I/usr/include/opencv2/highgui -L/usr/lib
 LIB = -lfcgi -lssl -lcrypto -lpthread -lm -lopencv_highgui -lopencv_core -lopencv_ml -lopencv_imgproc -lldap -lcrypt
 OBJ = log.o control.o data.o fastcgi.o main.o sensor.o actuator.o image.o bbb_pin.o pin_test.o login.o sensors/sensors.a actuators/actuators.a
 RM = rm -f
index 3e26e37..1c105db 100644 (file)
@@ -8,9 +8,6 @@
 // Files containing GPIO and PWM definitions
 #include "bbb_pin.h"
 
-
-
-
 /** Number of actuators **/
 int g_num_actuators = 0;
 
@@ -23,7 +20,7 @@ static Actuator g_actuators[ACTUATORS_MAX];
  * @param init - Function to call to initialise the actuator (may be NULL)
  * @returns Number of actuators added so far
  */
-int Actuator_Add(const char * name, int user_id, SetFn set, InitFn init, CleanFn cleanup, SanityFn sanity)
+int Actuator_Add(const char * name, int user_id, SetFn set, InitFn init, CleanFn cleanup, SanityFn sanity, double initial_value)
 {
        if (++g_num_actuators > ACTUATORS_MAX)
        {
@@ -36,12 +33,19 @@ int Actuator_Add(const char * name, int user_id, SetFn set, InitFn init, CleanFn
        a->name = name;
        a->set = set; // Set read function
        a->init = init; // Set init function
-       if (init != NULL)
-               init(name, user_id); // Call it
+
        a->sanity = sanity;
 
        pthread_mutex_init(&(a->mutex), NULL);
 
+       if (init != NULL)
+       {
+               if (!init(name, user_id))
+                       Fatal("Couldn't initialise actuator %s", name);
+       }
+
+       Actuator_SetValue(a, initial_value, false);
+
        return g_num_actuators;
 }
 
@@ -54,7 +58,7 @@ int Actuator_Add(const char * name, int user_id, SetFn set, InitFn init, CleanFn
 void Actuator_Init()
 {
        //Actuator_Add("ledtest",0,  Ledtest_Set, NULL,NULL,NULL);
-       Actuator_Add("filetest", 0, Filetest_Set, Filetest_Init, Filetest_Cleanup, Filetest_Sanity);
+       Actuator_Add("filetest", 0, Filetest_Set, Filetest_Init, Filetest_Cleanup, Filetest_Sanity, 0);
 }
 
 /**
@@ -72,12 +76,17 @@ void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg)
        {
                case CONTROL_START:
                        {
+                               // Set filename
                                char filename[BUFSIZ];
-                               const char *experiment_name = (const char*) arg;
+                               const char *experiment_path = (const char*) arg;
+                               int ret;
+
+                               ret = snprintf(filename, BUFSIZ, "%s/actuator_%d", experiment_path, a->id);
 
-                               if (snprintf(filename, BUFSIZ, "%s_a%d", experiment_name, a->id) >= BUFSIZ)
+                               if (ret >= BUFSIZ) 
                                {
-                                       Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ);
+                                       Fatal("Experiment path \"%s\" too long (%d, limit %d)",
+                                                       experiment_path, ret, BUFSIZ);
                                }
 
                                Log(LOGDEBUG, "Actuator %d with DataFile \"%s\"", a->id, filename);
@@ -134,7 +143,7 @@ void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg)
  */
 void Actuator_SetModeAll(ControlModes mode, void * arg)
 {
-       for (int i = 0; i < ACTUATORS_MAX; i++)
+       for (int i = 0; i < g_num_actuators; i++)
                Actuator_SetMode(&g_actuators[i], mode, arg);
 }
 
@@ -160,17 +169,22 @@ void * Actuator_Loop(void * arg)
                if (!a->activated)
                        break;
 
-               Actuator_SetValue(a, a->control.start);
+               Actuator_SetValue(a, a->control.start, true);
                // Currently does discrete steps after specified time intervals
-               while (a->control.steps > 0 && a->activated)
+
+               struct timespec wait;
+               DOUBLE_TO_TIMEVAL(a->control.stepsize, &wait);
+               while (!a->control_changed && a->control.steps > 0 && a->activated)
                {
-                       usleep(1e6*(a->control.stepwait));
+                       clock_nanosleep(CLOCK_MONOTONIC, 0, &wait, NULL);
                        a->control.start += a->control.stepsize;
-                       Actuator_SetValue(a, a->control.start);
+                       Actuator_SetValue(a, a->control.start, true);
                        
                        a->control.steps--;
                }
-               usleep(1e6*(a->control.stepwait));
+               if (a->control_changed)
+                       continue;
+               clock_nanosleep(CLOCK_MONOTONIC, 0, &wait, NULL);
 
                //TODO:
                // Note that although this loop has a sleep in it which would seem to make it hard to enforce urgent shutdowns,
@@ -206,12 +220,13 @@ void Actuator_SetControl(Actuator * a, ActuatorControl * c)
  * @param a - The Actuator
  * @param value - The value to set
  */
-void Actuator_SetValue(Actuator * a, double value)
+void Actuator_SetValue(Actuator * a, double value, bool record)
 {
        if (a->sanity != NULL && !a->sanity(a->user_id, value))
        {
                //ARE YOU INSANE?
-               Fatal("Insane value %lf for actuator %s", value, a->name);
+               Log(LOGERR,"Insane value %lf for actuator %s", value, a->name);
+               return;
        }
        if (!(a->set(a->user_id, value)))
        {
@@ -219,11 +234,19 @@ void Actuator_SetValue(Actuator * a, double value)
        }
 
        // Set time stamp
-       struct timeval t;
-       gettimeofday(&t, NULL);
-       // Record and save DataPoint
-       DataPoint d = {TIMEVAL_DIFF(t, *Control_GetStartTime()), value};
-       Data_Save(&(a->data_file), &d, 1);
+       struct timespec t;
+       clock_gettime(CLOCK_MONOTONIC, &t);
+       DataPoint d = {TIMEVAL_DIFF(t, *Control_GetStartTime()), a->last_setting.value};
+       // Record value change
+       if (record)
+       {       
+               d.time_stamp -= 1e-6;
+               Data_Save(&(a->data_file), &d, 1);
+               d.value = value;
+               d.time_stamp += 1e-6;
+               Data_Save(&(a->data_file), &d, 1);
+       }
+       a->last_setting = d;
 }
 
 /**
@@ -276,8 +299,8 @@ void Actuator_EndResponse(FCGIContext * context, Actuator * a, DataFormat format
  */
 void Actuator_Handler(FCGIContext * context, char * params)
 {
-       struct timeval now;
-       gettimeofday(&now, NULL);
+       struct timespec now;
+       clock_gettime(CLOCK_MONOTONIC, &now);
        double current_time = TIMEVAL_DIFF(now, *Control_GetStartTime());
        int id = 0;
        char * name = "";
@@ -336,7 +359,7 @@ void Actuator_Handler(FCGIContext * context, char * params)
                FCGI_RejectJSON(context, "No id or name supplied");
                return;
        }
-       else if (id < 0 || id >= ACTUATORS_MAX)
+       else if (id < 0 || id >= g_num_actuators)
        {
                FCGI_RejectJSON(context, "Invalid Actuator id");
                return;
@@ -364,7 +387,7 @@ void Actuator_Handler(FCGIContext * context, char * params)
                        //      If the user doesn't provide all 4 values, the Actuator will get set *once* using the first of the provided values
                        //      (see Actuator_Loop)
                        //  Not really a problem if n = 1, but maybe generate a warning for 2 <= n < 4 ?
-                       Log(LOGDEBUG, "Only provided %d values (expect %d) for Actuator setting", n);
+                       Log(LOGDEBUG, "Only provided %d values (expect %d) for Actuator setting", n, 4);
                }
                // SANITY CHECKS
                if (c.stepwait < 0 || c.steps < 0 || (a->sanity != NULL && !a->sanity(a->user_id, c.start)))
@@ -373,7 +396,6 @@ void Actuator_Handler(FCGIContext * context, char * params)
                        return;
                }
                Actuator_SetControl(a, &c);
-
        }
        
        // Begin response
index de81eea..f818526 100644 (file)
@@ -68,6 +68,8 @@ typedef struct
        SanityFn sanity;
        /** Cleanup function **/
        CleanFn clean;
+       /** Last setting **/
+       DataPoint last_setting;
        
 } Actuator;
 
@@ -77,7 +79,7 @@ extern void Actuator_SetModeAll(ControlModes mode, void *arg);
 extern void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg);
 
 extern void * Actuator_Loop(void * args); // Main loop for a thread that handles an Actuator
-extern void Actuator_SetValue(Actuator * a, double value); // Set an actuator by value
+extern void Actuator_SetValue(Actuator * a, double value, bool record); // Set an actuator by value
 extern void Actuator_SetControl(Actuator * a, ActuatorControl * c); // Set the control for an Actuator
 extern Actuator * Actuator_Identify(const char * str); // Identify a Sensor from a string Id
 
index 697753a..8dcc9df 100644 (file)
@@ -4,8 +4,10 @@ static FILE * f = NULL;
 bool Filetest_Init(const char * name, int id)
 {
        f = fopen(name, "w");
+       if (f == NULL)
+               return false;
        setbuf(f, NULL); // Unbuffer
-       return (f != NULL);
+       return true;
 }
 
 bool Filetest_Set(int id, double value)
index 2eb2d08..cb77dac 100644 (file)
@@ -136,6 +136,9 @@ extern const unsigned char g_pin_index_to_gpio[GPIO_NUM_PINS];
 #define ADC5 5
 #define ADC6 6
 #define ADC7 7
+#define ADC_VOLTAGE_MAX 1800
+#define ADC_RAW_MAX (2 << ADC_BITS)
+#define ADC_TO_MVOLTS(x) ((double)((x)/2 << ADC_BITS) * (double)ADC_VOLTAGE_MAX)
 
 /** Number of ADC pins **/
 #define ADC_NUM_PINS 8
index d4defd9..0875b34 100644 (file)
@@ -8,9 +8,9 @@
 
 /** Defines required to allow various C standard functions to be used **/
 #define _POSIX_C_SOURCE 200809L
-#define _BSD_SOURCE
+//#define _BSD_SOURCE
 #define _XOPEN_SOURCE 600
-
+#define _GNU_SOURCE
 /** Determine if we're running on the BBB **/
 #ifdef __arm__
        #define _BBB
@@ -21,6 +21,7 @@
 /** The current API version **/
 #define API_VERSION 0
 
+//#define REALTIME_VERSION
 
 
 
 #include <assert.h>
 #include <sys/time.h>
 #include <time.h>
+#include <string.h>
 
 #include "log.h"
 #include "fastcgi.h"
 #include "control.h"
 
 /**Converts a timeval to a double**/
-#define TIMEVAL_TO_DOUBLE(tv) ((tv).tv_sec + 1e-6 * ((tv).tv_usec))
+#define TIMEVAL_TO_DOUBLE(tv) ((tv).tv_sec + 1e-9 * ((tv).tv_nsec))
 /**Takes the tv1-tv2 between two timevals and returns the result as a double*/
-#define TIMEVAL_DIFF(tv1, tv2) ((tv1).tv_sec - (tv2).tv_sec + 1e-6 * ((tv1).tv_usec - (tv2).tv_usec))
-
+#define TIMEVAL_DIFF(tv1, tv2) ((tv1).tv_sec - (tv2).tv_sec + 1e-9 * ((tv1).tv_nsec - (tv2).tv_nsec))
+/** Converts a double time value (in seconds) to a timespec **/
+#define DOUBLE_TO_TIMEVAL(value, tv) { \
+                                                                               (tv)->tv_sec = (int)(value); \
+                                                                               (tv)->tv_nsec = ((value) - (int)(value))*1e9; \
+                                                                       }
 
 extern bool PathExists(const char * path);
-
+extern bool DirExists(const char * path);
 
 
 
index 0242a3b..6ebaba7 100644 (file)
@@ -3,18 +3,35 @@
  * @brief Handles all client control requests (admin related)
  */
 #include "common.h"
+#include "options.h"
 #include "control.h"
 #include "sensor.h"
 #include "actuator.h"
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 
 typedef struct ControlData {
        ControlModes current_mode;
        pthread_mutex_t mutex;
-       struct timeval start_time;
+       struct timespec start_time;
+       char user_name[31]; // The user who owns the currently running experiment
+       char experiment_dir[BUFSIZ]; //Directory for experiment
+       char experiment_name[BUFSIZ];
 } ControlData;
 
 ControlData g_controls = {CONTROL_STOP, PTHREAD_MUTEX_INITIALIZER, {0}};
 
+bool DirExists(const char *path)
+{
+       DIR *dir = opendir(path);
+       if (dir) {
+               closedir(dir);
+               return true;
+       }
+       return false;
+}
+
 bool PathExists(const char *path) 
 {
        FILE *fp = fopen(path, "r");
@@ -25,6 +42,43 @@ bool PathExists(const char *path)
        return false;
 }
 
+/**
+ * Lists all experiments for the current user.
+ * @param The context to work in
+ */
+void ListExperiments(FCGIContext *context) 
+{
+       DIR * dir = opendir(context->user_dir);
+       if (dir == NULL)
+       {
+               FCGI_RejectJSON(context, "Failed to open user directory");
+               return;
+       }
+       struct dirent * ent;
+       FCGI_BeginJSON(context, STATUS_OK);
+       FCGI_JSONKey("experiments");
+       FCGI_PrintRaw("[");
+
+       bool first = true;
+       while ((ent = readdir(dir)) != NULL) {
+               char *ext = strrchr(ent->d_name, '.');
+               if (ext && !strcmp(ext, ".exp")) {
+                       if (!first) {
+                               FCGI_PrintRaw(", ");
+                       }
+
+                       *ext = '\0'; // Ummm... probably not a great idea
+                       FCGI_PrintRaw("\"%s\"", ent->d_name);
+                       first = false;
+               }
+       }
+       FCGI_PrintRaw("]");
+       FCGI_EndJSON();
+       
+       closedir(dir);
+       return;
+}
+
 /**
  * System control handler. This covers high-level control, including
  * admin related functions and starting/stopping experiments..
@@ -36,21 +90,34 @@ void Control_Handler(FCGIContext *context, char *params) {
        const char *name = "";
        bool force = false;
        ControlModes desired_mode;
-
-
-
+       
        // Login/auth now handled entirely in fastcgi.c and login.c
        //TODO: Need to not have the ability for any user to stop someone else' experiment...
        // (achieve by storing the username of the person running the current experiment, even when they log out?)
        // (Our program should only realisitically support a single experiment at a time, so that should be sufficient)
-       FCGIValue values[4] = {
+       FCGIValue values[3] = {
                {"action", &action, FCGI_REQUIRED(FCGI_STRING_T)},
                {"force", &force, FCGI_BOOL_T},
                {"name", &name, FCGI_STRING_T}
        };
 
-       if (!FCGI_ParseRequest(context, params, values, 4))
+       if (!FCGI_ParseRequest(context, params, values, 3))
+               return;
+
+       if (!strcmp(action, "identify")) {
+               FCGI_BeginJSON(context, STATUS_OK);
+               FCGI_JSONLong("control_state_id", g_controls.current_mode);
+               FCGI_JSONPair("control_user_name", g_controls.user_name);
+               FCGI_JSONPair("control_experiment_name", g_controls.experiment_name);
+               FCGI_EndJSON();
+               return;
+       } else if (!strcmp(action, "list")) {
+               ListExperiments(context);
                return;
+       }
+       //TODO: Need a "load" action to set data files (but not run) from a past experiment
+
+       //TODO: Need a "delete" action so that people can overwrite experiments (without all this "force" shenanigans)
        
        if (!strcmp(action, "emergency")) {
                desired_mode = CONTROL_EMERGENCY;
@@ -66,24 +133,71 @@ void Control_Handler(FCGIContext *context, char *params) {
                FCGI_RejectJSON(context, "Unknown action specified.");
                return;
        }
-       
+
+       if ((*g_controls.user_name) != '\0' && strcmp(g_controls.user_name, context->user_name) != 0)
+       {
+               if (context->user_type != USER_ADMIN) {
+                       FCGI_RejectJSON(context, "Another user has an experiment in progress.");
+                       return;
+               }
+               
+               if (!force) {
+                       Log(LOGERR, "User %s is currently running an experiment!", g_controls.user_name);
+                       FCGI_RejectJSON(context, "Pass \"force\" to take control over another user's experiment");
+                       return;
+               }
+       }
+
        void *arg = NULL;
+       char experiment_dir[BUFSIZ] = {0};
        if (desired_mode == CONTROL_START) {
-               if (PathExists(name) && !force) {
+               int ret;
+
+               if (*name == '\0') {
+                       FCGI_RejectJSON(context, "An experiment name must be specified.");
+                       return;
+               } if (strpbrk(name, INVALID_CHARACTERS)) {
+                       FCGI_RejectJSON(context, 
+                               "An experiment name must not contain: " INVALID_CHARACTERS_JSON);
+                       return;
+               }
+
+               if (*(context->user_dir) == '\0') {
+                       FCGI_RejectJSON(context, "Not creating experiment in root dir.");
+                       return;
+               }
+
+               ret = snprintf(experiment_dir, BUFSIZ, "%s/%s.exp", 
+                                               context->user_dir, name);
+               if (ret >= BUFSIZ) {
+                       FCGI_RejectJSON(context, "The experiment name is too long.");
+                       return;
+               } else if (DirExists(experiment_dir) && !force) {
                        FCGI_RejectJSON(context, "An experiment with that name already exists.");
                        return;
                }
 
-               arg = (void*)name;
+               arg = (void*) experiment_dir;
        }
 
        const char *ret;
        if ((ret = Control_SetMode(desired_mode, arg)) != NULL) {
                FCGI_RejectJSON(context, ret);
        } else {
-               FCGI_BeginJSON(context, STATUS_OK);
-               FCGI_JSONPair("description", "ok");
-               FCGI_EndJSON();
+               if (desired_mode == CONTROL_STOP) {
+                       g_controls.user_name[0] = '\0';
+                       g_controls.experiment_dir[0] = '\0';
+                       g_controls.experiment_name[0] = '\0';
+               } else if (desired_mode == CONTROL_START) {
+                       snprintf(g_controls.user_name, sizeof(g_controls.user_name), 
+                                               "%s", context->user_name);
+                       snprintf(g_controls.experiment_dir, sizeof(g_controls.experiment_dir),
+                                               "%s", experiment_dir);
+                       snprintf(g_controls.experiment_name, sizeof(g_controls.experiment_name),
+                                               "%s", name);
+               }
+
+               FCGI_AcceptJSON(context, "Ok", NULL);
        }
 }
 
@@ -105,18 +219,13 @@ const char* Control_SetMode(ControlModes desired_mode, void * arg)
        else switch (desired_mode) {
                case CONTROL_START:
                        if (g_controls.current_mode == CONTROL_STOP) {
-                               const char * name = arg;
-                               if (!*name)
-                                       ret = "An experiment name must be specified";
-                               else if (strpbrk(name, INVALID_CHARACTERS))
-                                       ret = "The experiment name must not contain: " INVALID_CHARACTERS_JSON;
-                               else {
-                                       FILE *fp = fopen((const char*) arg, "a");
-                                       if (fp) {
-                                               fclose(fp);
-                                               gettimeofday(&(g_controls.start_time), NULL);
-                                       } else
-                                               ret = "Cannot open experiment name marker";
+                               const char * path = arg;
+                               if (mkdir(path, 0777) != 0 && errno != EEXIST) {
+                                       Log(LOGERR, "Couldn't create experiment directory %s - %s", 
+                                               path, strerror(errno));
+                                       ret = "Couldn't create experiment directory.";
+                               } else {
+                                       clock_gettime(CLOCK_MONOTONIC, &(g_controls.start_time));
                                }
                        } else 
                                ret = "Cannot start when not in a stopped state.";
@@ -131,7 +240,7 @@ const char* Control_SetMode(ControlModes desired_mode, void * arg)
                break;
                case CONTROL_EMERGENCY:
                        if (g_controls.current_mode != CONTROL_START) //pfft
-                               ret = "Not running so how can there be an emergency.";
+                               ret = "Not running so how can there be an emergency?";
                break;
                default:
                break;
@@ -171,6 +280,6 @@ const char * Control_GetModeName() {
  * Gets the start time for the current experiment
  * @return the start time
  */
-const struct timeval* Control_GetStartTime() {
+const struct timespec * Control_GetStartTime() {
        return &g_controls.start_time;
 }
index 7b3511a..7081c3f 100644 (file)
@@ -14,9 +14,9 @@ typedef enum ControlModes {
 } ControlModes;
 
 /** Invalid filename characters **/
-#define INVALID_CHARACTERS "\"*/:<>?\\|"
+#define INVALID_CHARACTERS "\"*/:<>?\\|"
 /** The same as INVALID_CHARACTERS, except escaped for use in JSON strings **/
-#define INVALID_CHARACTERS_JSON "\\\"*/:<>?\\\\|"
+#define INVALID_CHARACTERS_JSON "\\\"*/:<>?\\\\|"
 
 extern void Control_Handler(FCGIContext *context, char *params);
 extern const char* Control_SetMode(ControlModes desired_mode, void * arg);
@@ -24,6 +24,6 @@ extern ControlModes Control_GetMode();
 extern const char * Control_GetModeName();
 //extern bool Control_Lock();
 //extern void Control_Unlock();
-extern const struct timeval* Control_GetStartTime();
+extern const struct timespec* Control_GetStartTime();
 
 #endif
index dae3172..edfd4c3 100644 (file)
@@ -161,13 +161,13 @@ void Data_PrintByIndexes(DataFile * df, int start_index, int end_index, DataForm
        switch (format)
        {
                case JSON:
-                       fmt_string = "[%f,%f]";
+                       fmt_string = "[%.9f,%f]";
                        separator = ',';
                        // For JSON we need an opening bracket
                        FCGI_PrintRaw("["); 
                        break;
                case TSV:
-                       fmt_string = "%f\t%f";
+                       fmt_string = "%.9f\t%f";
                        separator = '\n';
                        break;
        }
index fe461b8..0a6783f 100644 (file)
@@ -9,6 +9,8 @@
 #include <fcgi_stdio.h>
 #include <openssl/sha.h>
 #include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 
 #include "common.h"
 #include "sensor.h"
  */ 
 static void IdentifyHandler(FCGIContext *context, char *params) {
        bool ident_sensors = false, ident_actuators = false;
-       bool has_control = FCGI_HasControl(context, getenv("COOKIE_STRING"));
+       char control_key[CONTROL_KEY_BUFSIZ];
+       bool has_control;
        int i;
 
+       snprintf(control_key, CONTROL_KEY_BUFSIZ, "%s", getenv("COOKIE_STRING"));
+       has_control = FCGI_HasControl(context, control_key);
+       
        FCGIValue values[2] = {{"sensors", &ident_sensors, FCGI_BOOL_T},
                                         {"actuators", &ident_actuators, FCGI_BOOL_T}};
        if (!FCGI_ParseRequest(context, params, values, 2))
@@ -44,9 +50,14 @@ static void IdentifyHandler(FCGIContext *context, char *params) {
        FCGI_BeginJSON(context, STATUS_OK);
        FCGI_JSONPair("description", "MCTX3420 Server API (2013)");
        FCGI_JSONPair("build_date", __DATE__ " " __TIME__);
+       struct timespec t;
+       t.tv_sec = 0; t.tv_nsec = 0;
+       clock_getres(CLOCK_MONOTONIC, &t);
+       FCGI_JSONDouble("clock_getres", TIMEVAL_TO_DOUBLE(t));
        FCGI_JSONLong("api_version", API_VERSION);
        FCGI_JSONBool("logged_in", has_control);
-       FCGI_JSONPair("friendly_name", has_control ? context->friendly_name : "");
+       FCGI_JSONPair("user_name", has_control ? context->user_name : "");
+       
 
        //Sensor and actuator information
        if (ident_sensors) {
@@ -75,36 +86,75 @@ static void IdentifyHandler(FCGIContext *context, char *params) {
 }
 
 /**
- * Gives the user a key that determines who has control over
- * the system at any one time. The key can be forcibly generated, revoking
- * any previous control keys. To be used in conjunction with HTTP 
- * basic authentication.
+ * Given an authorised user, attempt to set the control over the system.
+ * Modifies members in the context structure appropriately if successful.
  * @param context The context to work in
- * @param force Whether to force key generation or not.
- * @return true on success, false otherwise (eg someone else already in control)
+ * @param user_name - Name of the user
+ * @param user_type - Type of the user, passed after successful authentication
+ * @return true on success, false otherwise (eg someone else  already in control)
  */
-bool FCGI_LockControl(FCGIContext *context, bool force) {
+bool FCGI_LockControl(FCGIContext *context, const char * user_name, UserType user_type) 
+{
+       // Get current time
        time_t now = time(NULL);
        bool expired = now - context->control_timestamp > CONTROL_TIMEOUT;
+       int i;
 
-       if (force || !*(context->control_key) || expired) 
+       // Can't lock control if: User not actually logged in (sanity), or key is still valid and the user is not an admin
+       if (user_type == USER_UNAUTH || 
+               (user_type != USER_ADMIN && !expired && *(context->control_key) != '\0'))
+               return false;
+
+       // Release any existing control (if any)
+       FCGI_ReleaseControl(context);
+
+       // Set timestamp
+       context->control_timestamp = now;
+
+       // Generate a SHA1 hash for the user
+       SHA_CTX sha1ctx;
+       unsigned char sha1[20];
+       i = rand();
+       SHA1_Init(&sha1ctx);
+       SHA1_Update(&sha1ctx, &now, sizeof(now));
+       SHA1_Update(&sha1ctx, &i, sizeof(i));
+       SHA1_Final(sha1, &sha1ctx);
+       for (i = 0; i < sizeof(sha1); i++)
+               sprintf(context->control_key + i * 2, "%02x", sha1[i]);
+
+       // Set the IPv4 address
+       snprintf(context->control_ip, 16, "%s", getenv("REMOTE_ADDR"));
+
+       // Set the user name
+       int uname_len = strlen(user_name);
+       i = snprintf(context->user_name, sizeof(context->user_name), "%s", user_name);
+       if (i < uname_len) {
+               Log(LOGERR, "Username at %d characters too long (limit %d)", 
+                       uname_len, sizeof(context->user_name));
+               return false; // :-(
+       }
+       // Set the user type
+       context->user_type = user_type;
+
+       // Build the user directory
+       i = snprintf(context->user_dir, sizeof(context->user_dir), "%s/%s", 
+                                       g_options.experiment_dir, context->user_name);
+       if (i >= sizeof(context->user_dir)) {
+               Log(LOGERR, "Experiment dir too long (required %d, limit %d)",
+                       i, sizeof(context->user_dir));
+               return false;
+       }
+
+       Log(LOGDEBUG, "User dir: %s", context->user_dir);
+       // Create directory
+       if (mkdir(context->user_dir, 0777) != 0 && errno != EEXIST)
        {
-               SHA_CTX sha1ctx;
-               unsigned char sha1[20];
-               int i = rand();
-
-               SHA1_Init(&sha1ctx);
-               SHA1_Update(&sha1ctx, &now, sizeof(now));
-               SHA1_Update(&sha1ctx, &i, sizeof(i));
-               SHA1_Final(sha1, &sha1ctx);
-
-               context->control_timestamp = now;
-               for (i = 0; i < 20; i++)
-                       sprintf(context->control_key + i * 2, "%02x", sha1[i]);
-               snprintf(context->control_ip, 16, "%s", getenv("REMOTE_ADDR"));
-               return true;
+               Log(LOGERR, "Couldn't create user directory %s - %s", 
+                       context->user_dir, strerror(errno));
+               return false; // :-(
        }
-       return false;
+
+       return true; // :-)
 }
 
 /**
@@ -133,6 +183,7 @@ bool FCGI_HasControl(FCGIContext *context, const char *key) {
  */
 void FCGI_ReleaseControl(FCGIContext *context) {
        *(context->control_key) = 0;
+       // Note: context->user_name should *not* be cleared
        return;
 }
 
@@ -282,8 +333,8 @@ void FCGI_BeginJSON(FCGIContext *context, StatusCodes status_code)
        printf("\t\"module\" : \"%s\"", context->current_module);
        FCGI_JSONLong("status", status_code);
        //Time and running statistics
-       struct timeval now;
-       gettimeofday(&now, NULL);
+       struct timespec now;
+       clock_gettime(CLOCK_MONOTONIC, &now);
        FCGI_JSONDouble("start_time", TIMEVAL_TO_DOUBLE(g_options.start_time));
        FCGI_JSONDouble("current_time", TIMEVAL_TO_DOUBLE(now));
        FCGI_JSONDouble("running_time", TIMEVAL_DIFF(now, g_options.start_time));
@@ -337,7 +388,7 @@ void FCGI_JSONLong(const char *key, long value)
  */
 void FCGI_JSONDouble(const char *key, double value)
 {
-       printf(",\r\n\t\"%s\" : %f", key, value);
+       printf(",\r\n\t\"%s\" : %.9f", key, value);
 }
 
 /**
@@ -463,16 +514,17 @@ void * FCGI_RequestLoop (void *data)
        while (FCGI_Accept() >= 0) {
                
                ModuleHandler module_handler = NULL;
-               char module[BUFSIZ], params[BUFSIZ];
-               //Don't need to copy if we're not modifying string contents
-               const char *cookie = getenv("COOKIE_STRING");
+               char module[BUFSIZ], params[BUFSIZ], control_key[CONTROL_KEY_BUFSIZ];
                
                //strncpy doesn't zero-truncate properly
                snprintf(module, BUFSIZ, "%s", getenv("DOCUMENT_URI_LOCAL"));
                snprintf(params, BUFSIZ, "%s", getenv("QUERY_STRING"));
 
+               //Hack to get the nameless cookie only
+               snprintf(control_key, CONTROL_KEY_BUFSIZ, "%s", getenv("COOKIE_STRING"));
+
                Log(LOGDEBUG, "Got request #%d - Module %s, params %s", context.response_number, module, params);
-               Log(LOGDEBUG, "Cookie: %s", cookie);
+               Log(LOGDEBUG, "Control key: %s", control_key);
 
                
                //Remove trailing slashes (if present) from module query
@@ -507,18 +559,12 @@ void * FCGI_RequestLoop (void *data)
                
                if (module_handler) 
                {
-                       //if (module_handler != Login_Handler && module_handler != IdentifyHandler)
+                       //if (module_handler != Login_Handler && module_handler != IdentifyHandler && module_handler)
                        if (false) // Testing
                        {
-                               if (cookie[0] == '\0')
-                               {
-                                       FCGI_RejectJSONEx(&context, STATUS_UNAUTHORIZED, "Please login.");
-                                       continue;
-                               }
-
-                               if (!FCGI_HasControl(&context, cookie))
+                               if (!FCGI_HasControl(&context, control_key))
                                {
-                                       FCGI_RejectJSON(&context, "Invalid control key.");
+                                       FCGI_RejectJSON(&context, "Please login. Invalid control key.");
                                        continue;       
                                }
 
@@ -533,9 +579,6 @@ void * FCGI_RequestLoop (void *data)
                {
                        FCGI_RejectJSON(&context, "Unhandled module");
                }
-               
-
-               
        }
 
        Log(LOGDEBUG, "Thread exiting.");
index 269bb94..de02501 100644 (file)
@@ -33,23 +33,31 @@ typedef enum StatusCodes {
 #define FCGI_RECEIVED(x) ((x) & FCGI_PARAM_RECEIVED)
 #define FCGI_TYPE(x) ((x) & ~(FCGI_PARAM_REQUIRED | FCGI_PARAM_RECEIVED))
 
+#define CONTROL_KEY_BUFSIZ 41
+
 typedef struct FCGIValue {
        const char *key;
        void *value;
        unsigned flags;
 } FCGIValue;
 
+typedef enum {USER_UNAUTH, USER_NORMAL, USER_ADMIN} UserType;
+
 /**Contextual information related to FCGI requests*/
 typedef struct  
 {
        /**The time of last valid user access possessing the control key**/
        time_t control_timestamp;
        /**A SHA-1 hash that is the control key, determining who is logged in**/
-       char control_key[41];
+       char control_key[CONTROL_KEY_BUFSIZ]; 
        /**The IPv4 address of the logged-in user**/
        char control_ip[16];
-       /**A friendly name for the logged-in user. Max length 30**/
-       char friendly_name[31];
+       /**Determines if the user is an admin or not**/
+       UserType user_type;
+       /**Name of the logged in user**/
+       char user_name[31];
+       /**User directory for the logged in user**/
+       char user_dir[BUFSIZ];
        /**The name of the current module**/
        const char *current_module;
        /**For debugging purposes?**/
@@ -58,7 +66,7 @@ typedef struct
 
 typedef void (*ModuleHandler) (FCGIContext *context, char *params);
 
-extern bool FCGI_LockControl(FCGIContext *context, bool force);
+extern bool FCGI_LockControl(FCGIContext *context, const char * user_name, UserType user_type);
 extern void FCGI_ReleaseControl(FCGIContext *context);
 extern bool FCGI_HasControl(FCGIContext *context, const char *key);
 extern char *FCGI_KeyPair(char *in, const char **key, const char **value);
index c7b6263..1d35264 100644 (file)
@@ -4,15 +4,38 @@
 #include <string.h>
 #include <stdio.h>
 
+CvCapture *capture;
+int captureID = -1;
+
 void Image_Handler(FCGIContext * context, char * params)
 {
-       static CvCapture * capture = NULL;
-       if (capture == NULL) {
-               capture = cvCreateCameraCapture(0);
-               //limit resolution to work on bbb
-               cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH, 352);
-               cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT, 288);
+       int num = 0, width = 800, height = 600;
+       FCGIValue val[] = {
+               {"num", &num, FCGI_INT_T},
+               {"width", &width, FCGI_INT_T},
+               {"height", &height, FCGI_INT_T}
+       };
+       if (!FCGI_ParseRequest(context, params, val, 3))
+               return;
+       else if (num < 0 || num > 1) {
+               FCGI_RejectJSON(context, "Invalid capture number");
+               return;
+       } else if (width <= 0 || height <= 0) {
+               FCGI_RejectJSON(context, "Invalid width/height");
+               return;
+       }
+
+       if (captureID != num) {
+               if (captureID >= 0) {
+                       cvReleaseCapture(&capture);
+               }
+               capture = cvCreateCameraCapture(num);
+               captureID = num;
        }
+
+       cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH, width);
+       cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT, height);
+
        static int p[] = {CV_IMWRITE_JPEG_QUALITY, 100, 0};
 
        IplImage * frame = cvQueryFrame(capture);
index b62a7bd..3c1ab69 100644 (file)
@@ -8,7 +8,9 @@
 
 #include "common.h"
 
+extern void Image_Init();
 extern void Image_Handler(FCGIContext * context, char * params); 
+extern void Image_Cleanup();
 
 #endif //_IMAGE_H
 
index a2a11e9..78e31f9 100644 (file)
 
 /**
  * Attempt to login using a file formatted like /etc/shadow
- * This is here for horrible hack purposes
+ * This is here... because all better options have been exhausted
  * @param user - The username
  * @param pass - The password
- * @returns True if the login was successful, false otherwise
+ * @returns Privelage level of the user or USER_UNAUTH for failure to authenticate
  */
-bool Login_Shadow(const char * user, const char * pass, const char * shadow)
+UserType Login_Shadow(const char * user, const char * pass, const char * shadow)
 {
        if (strlen(user) + strlen(pass) >= BUFSIZ-1)
        {
                Log(LOGERR, "User/Password too long!\n");
-               return false;
+               return USER_UNAUTH;
        }
 
        FILE * f = fopen(shadow, "r");
        if (f == NULL)
        {
                Log(LOGERR,"Can't open %s - %s\n", shadow, strerror(errno));
-               return false;
+               return USER_UNAUTH;
        }
 
        char buffer[BUFSIZ];
@@ -42,9 +42,9 @@ bool Login_Shadow(const char * user, const char * pass, const char * shadow)
        while (fgets(buffer, BUFSIZ, f) != NULL) // NOTE: Restrict username+password strings to BUFSIZ... what could possibly go wrong?
        {
 
-               Log(LOGDEBUG,"Scanning %d: %s", strlen(buffer), buffer);
+               //Log(LOGDEBUG,"Scanning %d: %s", strlen(buffer), buffer);
        
-               for (int i = 0; i < BUFSIZ-1; ++i)
+               for (int i = 0; i < BUFSIZ-1 && buffer[i] != '\0'; ++i)
                {
                        if (buffer[i] == ':')
                        {
@@ -56,7 +56,7 @@ bool Login_Shadow(const char * user, const char * pass, const char * shadow)
 
                if (strcmp(user,buffer) == 0)
                {
-                       Log(LOGDEBUG,"User matches! %s\n", buffer);
+                       //Log(LOGDEBUG,"User matches! %s\n", buffer);
                        break;
                } 
                passwd_index = -1;
@@ -64,16 +64,31 @@ bool Login_Shadow(const char * user, const char * pass, const char * shadow)
 
        if (passwd_index <= 0)
        {
-               Log(LOGDEBUG,"No user found matching %s\n", user);
-               return false;
+               //Log(LOGDEBUG,"No user found matching %s\n", user);
+               return USER_UNAUTH;
        }
 
-       for (int i = passwd_index; i < BUFSIZ-1; ++i)
+       int gid_index = -1;
+       for (int i = passwd_index; i < BUFSIZ-1 && buffer[i] != '\0'; ++i)
        {
-               if (buffer[i] == ':' || buffer[i] == '\n')
+               if (buffer[i] == ':')
                {
+                       gid_index = i+1;
+                       buffer[i] = '\0';
+               }
+               if (buffer[i] == '\n')
                        buffer[i] = '\0';
-                       
+       }
+       char * end = buffer+gid_index;
+       UserType user_type = USER_NORMAL;
+       if (gid_index > passwd_index && gid_index < BUFSIZ-1)
+       {
+               int gid = strtol(buffer+gid_index, &end,10);
+               Log(LOGDEBUG, "Usertype %d %s", gid, buffer+gid_index);
+               if (*end == '\0' && gid == 0)
+               {
+                       Log(LOGDEBUG, "Admin");
+                       user_type = USER_ADMIN;
                }
        }
        
@@ -87,10 +102,14 @@ bool Login_Shadow(const char * user, const char * pass, const char * shadow)
                        break;
        }
 
-       Log(LOGDEBUG,"Salted Entry: %s\n", buffer+passwd_index);
-       Log(LOGDEBUG,"Salted Attempt: %s\n", crypt(pass, salt));
+//     Log(LOGDEBUG,"Salted Entry: %s\n", buffer+passwd_index);
+//     Log(LOGDEBUG,"Salted Attempt: %s\n", crypt(pass, salt));
        
-       return (strcmp(crypt(pass, salt), buffer+passwd_index) == 0);
+       if (strcmp(crypt(pass, salt), buffer+passwd_index) == 0)
+       {
+               return user_type;
+       }
+       return USER_UNAUTH;
 }
 
 /**
@@ -193,7 +212,7 @@ void Login_Handler(FCGIContext * context, char * params)
        user[i] = '\0';
 
        
-       bool authenticated = true;
+       UserType user_type = USER_UNAUTH;
        
        switch (g_options.auth_method)
        {
@@ -213,10 +232,10 @@ void Login_Handler(FCGIContext * context, char * params)
                        //int len = sprintf(dn, "uid=%s,%s", user, g_options.ldap_base_dn);
        
                        // At UWA (hooray)
-                       char * user_type = "Students";
+                       char * user_group = "Students";
                        if (user[0] == '0')
-                               user_type = "Staff";
-                       int len = sprintf(dn, "cn=%s,ou=%s,%s", user, user_type, g_options.ldap_base_dn);
+                               user_group = "Staff";
+                       int len = sprintf(dn, "cn=%s,ou=%s,%s", user, user_group, g_options.ldap_base_dn);
                
 
                        if (len >= BUFSIZ)
@@ -225,41 +244,49 @@ void Login_Handler(FCGIContext * context, char * params)
                                return;
                        }
                
-                       authenticated = (Login_LDAP_Bind(g_options.auth_uri, dn, pass) == LDAP_SUCCESS);
+                       if (Login_LDAP_Bind(g_options.auth_uri, dn, pass) == LDAP_SUCCESS)
+                       {
+                               if (user[0] == '0')
+                                       user_type = USER_ADMIN;
+                               else
+                                       user_type = USER_NORMAL;
+                       }
                        break;
                }
                case AUTH_SHADOW:
                {
-                       authenticated = Login_Shadow(user, pass, g_options.auth_uri);
+                       user_type = Login_Shadow(user, pass, g_options.auth_uri);
                        break;
                }
                default:
                {
                        Log(LOGWARN, "No authentication!");
+                       user_type = USER_ADMIN;
                        break;
                }
        }
                
        // error check  
        
-       if (!authenticated)
+       if (user_type == USER_UNAUTH)
        {
+               Log(LOGDEBUG, "Authentication failure for %s", user);
                FCGI_RejectJSONEx(context, STATUS_UNAUTHORIZED, "Authentication failure.");
        }
        else
        {
-               if (FCGI_LockControl(context, false))
+               // Try and gain control over the system
+               if (FCGI_LockControl(context, user, user_type))
                {
-                       //Todo: change this to something better than the username if using LDAP.
-                       snprintf(context->friendly_name, 31, "%s", user);
-                       FCGI_EscapeText(context->friendly_name); //Don't break javascript pls
-
+                       FCGI_EscapeText(context->user_name); //Don't break javascript pls
                        // Give the user a cookie
                        FCGI_AcceptJSON(context, "Logged in", context->control_key);
+                       Log(LOGDEBUG, "Successful authentication for %s", user);
                }
                else
                {
-                       FCGI_RejectJSON(context, "Someone else is already logged in");
+                       Log(LOGDEBUG, "%s successfully authenticated but system was in use by %s", user, context->user_name);
+                       FCGI_RejectJSON(context, "Someone else is already logged in (and you are not an admin)");
                }
        }
 }
index c2e5161..fadcd3a 100644 (file)
 #include <signal.h> // for signal handling
 
 
+#ifdef REALTIME_VERSION
+#include <time.h>
+#include <sched.h>
+#include <sys/mman.h>
+#include <sys/utsname.h>
+#endif //REALTIME_VERSION
+
 // --- Variable definitions --- //
 Options g_options; // options passed to program through command line arguments
 
@@ -29,19 +36,20 @@ Options g_options; // options passed to program through command line arguments
  */
 void ParseArguments(int argc, char ** argv)
 {
-
-
        g_options.program = argv[0]; // program name
        g_options.verbosity = LOGDEBUG; // default log level
-       gettimeofday(&(g_options.start_time), NULL); // Start time
+       // Set the main directory
+       //if (getcwd(g_options.root_dir, sizeof(g_options.root_dir)) == NULL)
+       //      Fatal("Couldn't get current working directory - %s", strerror(errno));
+
+       clock_gettime(CLOCK_MONOTONIC, &(g_options.start_time)); // Start time
 
 
        g_options.auth_method = AUTH_NONE;  // Don't use authentication
        g_options.auth_uri = ""; // 
        g_options.ldap_base_dn = "";
+       g_options.experiment_dir = ".";
        
-
-
        for (int i = 1; i < argc; ++i)
        {
                if (argv[i][0] != '-')
@@ -72,6 +80,10 @@ void ParseArguments(int argc, char ** argv)
                        case 'd':
                                g_options.ldap_base_dn = argv[++i];
                                break;
+                       case 'e':
+                       // Experiments directory
+                               g_options.experiment_dir = argv[++i];
+                               break;
                        default:
                                Fatal("Unrecognised switch %s", argv[i]);
                                break;
@@ -85,6 +97,13 @@ void ParseArguments(int argc, char ** argv)
        Log(LOGDEBUG, "Pin Module Enabled: %d", g_options.enable_pin);
        Log(LOGDEBUG, "Auth URI: %s", g_options.auth_uri);
        Log(LOGDEBUG, "LDAP Base DN: %s", g_options.ldap_base_dn);
+       //Log(LOGDEBUG, "Root directory: %s", g_options.root_dir);
+       Log(LOGDEBUG, "Experiment directory: %s", g_options.experiment_dir);
+
+       if (!DirExists(g_options.experiment_dir))
+       {
+               Fatal("Experiment directory '%s' does not exist.", g_options.experiment_dir);
+       }
 
        if (g_options.auth_uri[0] != '\0')
        {
@@ -108,6 +127,38 @@ void Cleanup()
        Log(LOGDEBUG, "Finish cleanup.");
 }
 
+
+#ifdef REALTIME_VERSION
+
+#define MAX_SAFE_STACK (8*1024)
+#define NSEC_PER_SEC (1000000000)
+
+void stack_prefault()
+{
+       unsigned char dummy[MAX_SAFE_STACK];
+       memset(dummy, 0, MAX_SAFE_STACK);
+       return;
+}
+
+bool is_realtime()
+{
+       struct utsname u;
+       bool crit1 = 0;
+       bool crit2 = 0;
+       FILE * f;
+       uname(&u);
+       crit1 = (strcasestr(u.version, "PREEMPT RT") != NULL);
+       if (crit1 && ((f = fopen("/sys/kernel/realtime", "r")) != NULL))
+       {
+               int flag;
+               crit2 = ((fscanf(f, "%d", &flag) == 1) && (flag == 1));
+               fclose(f);
+       }
+       return (crit1 && crit2);
+}
+
+#endif //REALTIME_VERSION
+
 /**
  * Main entry point; start worker threads, setup signal handling, wait for threads to exit, exit
  * @param argc - Num args
@@ -117,30 +168,55 @@ void Cleanup()
  */
 int main(int argc, char ** argv)
 {
+
        // Open log before calling ParseArguments (since ParseArguments may call the Log functions)
        openlog("mctxserv", LOG_PID | LOG_PERROR, LOG_USER);
-       Log(LOGINFO, "Server started");
 
        ParseArguments(argc, argv); // Setup the g_options structure from program arguments
 
+       Log(LOGINFO, "Server started");
+
+
+       
+       #ifdef REALTIME_VERSION
+       
+       if (is_realtime())
+       {
+               Log(LOGDEBUG, "Running under realtime kernel");
+       }
+       else
+       {
+               Fatal("Not running under realtime kernel");
+       }
+       struct sched_param param;
+       param.sched_priority = 49;
+       if (sched_setscheduler(0, SCHED_FIFO, &param) < 0)
+               Fatal("sched_setscheduler failed - %s", strerror(errno));
+       if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1)
+               Fatal("mlockall failed - %s", strerror(errno));
+       stack_prefault();
+       #endif //REALTIME_VERSION
+
+       
+
        Sensor_Init();
        Actuator_Init();
        Pin_Init();
        
        // Try and start things
-       /*
+       
        const char *ret;
        if ((ret = Control_SetMode(CONTROL_START, "test")) != NULL)
                Fatal("Control_SetMode failed with '%s'", ret);
-       */
+       
 
        // run request thread in the main thread
        FCGI_RequestLoop(NULL);
 
-       /*
+       
        if ((ret = Control_SetMode(CONTROL_STOP, "test")) != NULL)
                Fatal("Control_SetMode failed with '%s'", ret);
-       */
+       
        //Sensor_StopAll();
        //Actuator_StopAll();
 
index 839bc4d..0e0b598 100644 (file)
@@ -15,9 +15,9 @@ typedef struct
        /** Determines at what level log messages are shown **/
        int verbosity;
        /** Time at which program begins to run **/
-       struct timeval start_time;
+       struct timespec start_time;
        /** Time at which program exits **/
-       struct timeval end_time;
+       struct timespec end_time;
 
        /** Whether or not to enable the pin_test module **/
        bool enable_pin;
@@ -31,8 +31,8 @@ typedef struct
        /** Authentication method **/
        enum {AUTH_NONE, AUTH_LDAP, AUTH_SHADOW} auth_method;
 
-       
-
+       /** Experiments directory **/
+       const char *experiment_dir;
 } Options;
 
 /** The only instance of the Options struct **/
index 2fa6890..da48239 100644 (file)
@@ -22,7 +22,8 @@ pin_test="0"
 # Set to the URI to use authentication
 #auth_uri="ldap://192.168.1.1"
 #auth_uri="ldaps://ldap.pheme.uwa.edu.au" #UWA
-auth_uri="/etc/shadow"
+#auth_uri="/etc/shadow"
+auth_uri="shadow"
 
 # Set to the dn of the LDAP server
 ldap_base_dn="ou=People,dc=daedalus" # Testing
index 792bbbd..ae5cdf4 100644 (file)
@@ -29,7 +29,7 @@ int g_num_sensors = 0;
  * @param min_warn - Minimum warning threshold; program will log warnings if the value falls below this threshold
  * @returns Number of actuators added so far
  */
-int Sensor_Add(const char * name, int user_id, ReadFn read, InitFn init, CleanFn cleanup, double max_error, double min_error, double max_warn, double min_warn)
+int Sensor_Add(const char * name, int user_id, ReadFn read, InitFn init, CleanFn cleanup, SanityFn sanity)
 {
        if (++g_num_sensors > SENSORS_MAX)
        {
@@ -45,19 +45,23 @@ int Sensor_Add(const char * name, int user_id, ReadFn read, InitFn init, CleanFn
        s->name = name;
        s->read = read; // Set read function
        s->init = init; // Set init function
-       if (init != NULL)
-               init(name, user_id); // Call it
 
        // Start by averaging values taken over a second
-       s->sample_us = 1e6;
+       DOUBLE_TO_TIMEVAL(1e-4, &(s->sample_time));
        s->averages = 1;
+       s->num_read = 0;
 
-       // Set warning/error thresholds
-       s->thresholds.max_error = max_error;
-       s->thresholds.min_error = min_error;
-       s->thresholds.max_warn = max_warn;
-       s->thresholds.min_warn = min_warn;
+       // Set sanity function
+       s->sanity = sanity;
 
+       if (init != NULL)
+       {
+               if (!init(name, user_id))
+                       Fatal("Couldn't init sensor %s", name);
+       }
+
+       s->current_data.time_stamp = 0;
+       s->current_data.value = 0;
        return g_num_sensors;
 }
 
@@ -68,11 +72,14 @@ int Sensor_Add(const char * name, int user_id, ReadFn read, InitFn init, CleanFn
  */
 #include "sensors/resource.h"
 #include "sensors/strain.h"
-#include "sensors/piped.h"
+#include "sensors/pressure.h"
 void Sensor_Init()
 {
-       Sensor_Add("cpu_stime", RESOURCE_CPU_SYS, Resource_Read, NULL, NULL, 1e50,-1e50,1e50,-1e50);    
-       Sensor_Add("cpu_utime", RESOURCE_CPU_USER, Resource_Read, NULL, NULL, 1e50,-1e50,1e50,-1e50);   
+       Sensor_Add("cpu_stime", RESOURCE_CPU_SYS, Resource_Read, NULL, NULL, NULL);     
+       Sensor_Add("cpu_utime", RESOURCE_CPU_USER, Resource_Read, NULL, NULL, NULL);    
+       //Sensor_Add("pressure_high0", PRES_HIGH0, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
+       //Sensor_Add("pressure_high1", PRES_HIGH1, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
+       //Sensor_Add("pressure_low0", PRES_LOW0, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
        //Sensor_Add("../testing/count.py", 0, Piped_Read, Piped_Init, Piped_Cleanup, 1e50,-1e50,1e50,-1e50);
        //Sensor_Add("strain0", STRAIN0, Strain_Read, Strain_Init, 5000,0,5000,0);
        //Sensor_Add("strain1", STRAIN1, Strain_Read, Strain_Init, 5000,0,5000,0);
@@ -115,11 +122,15 @@ void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg)
                        {
                                // Set filename
                                char filename[BUFSIZ];
-                               const char *experiment_name = (const char*) arg;
+                               const char *experiment_path = (const char*) arg;
+                               int ret;
+
+                               ret = snprintf(filename, BUFSIZ, "%s/sensor_%d", experiment_path, s->id);
 
-                               if (snprintf(filename, BUFSIZ, "%s_%d", experiment_name, s->id) >= BUFSIZ)
+                               if (ret >= BUFSIZ) 
                                {
-                                       Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ);
+                                       Fatal("Experiment path \"%s\" too long (%d, limit %d)",
+                                                       experiment_path, ret, BUFSIZ);
                                }
 
                                Log(LOGDEBUG, "Sensor %d with DataFile \"%s\"", s->id, filename);
@@ -177,26 +188,6 @@ void Sensor_SetModeAll(ControlModes mode, void * arg)
 }
 
 
-/**
- * Checks the sensor data for unsafe or unexpected results 
- * @param sensor_id - The ID of the sensor
- * @param value - The value from the sensor to test
- */
-void Sensor_CheckData(Sensor * s, double value)
-{
-       if( value > s->thresholds.max_error || value < s->thresholds.min_error)
-       {
-               Log(LOGERR, "Sensor %s at %f is above or below its safety value of %f or %f\n",s->name,value, s->thresholds.max_error, s->thresholds.min_error);
-               //new function that stops actuators?
-               //Control_SetMode(CONTROL_EMERGENCY, NULL)
-       }
-       else if( value > s->thresholds.max_warn || value < s->thresholds.min_warn)
-       {
-               Log(LOGWARN, "Sensor %s at %f is above or below its warning value of %f or %f\n", s->name,value,s->thresholds.max_warn, s->thresholds.min_warn);        
-       }
-}
-
-
 /**
  * Record data from a single Sensor; to be run in a seperate thread
  * @param arg - Cast to Sensor* - Sensor that the thread will handle
@@ -214,21 +205,38 @@ void * Sensor_Loop(void * arg)
                d.value = 0;
                bool success = s->read(s->user_id, &(d.value));
 
-               struct timeval t;
-               gettimeofday(&t, NULL);
+               struct timespec t;
+               clock_gettime(CLOCK_MONOTONIC, &t);
                d.time_stamp = TIMEVAL_DIFF(t, *Control_GetStartTime());        
                
                if (success)
                {
-
-
-                       Sensor_CheckData(s, d.value);
-                       Data_Save(&(s->data_file), &d, 1); // Record it
+                       if (s->sanity != NULL)
+                       {
+                               if (!s->sanity(s->user_id, d.value))
+                               {
+                                       Fatal("Sensor %s (%d,%d) reads unsafe value", s->name, s->id, s->user_id);
+                               }
+                       }
+                       s->current_data.time_stamp += d.time_stamp;
+                       s->current_data.value += d.value;
+                       
+                       if (++(s->num_read) >= s->averages)
+                       {
+                               s->current_data.time_stamp /= s->averages;
+                               s->current_data.value /= s->averages;
+                               Data_Save(&(s->data_file), &(s->current_data), 1); // Record it
+                               s->num_read = 0;
+                               s->current_data.time_stamp = 0;
+                               s->current_data.value = 0;
+                       }
                }
                else
                        Log(LOGWARN, "Failed to read sensor %s (%d,%d)", s->name, s->id,s->user_id);
 
-               usleep(s->sample_us);
+
+               clock_nanosleep(CLOCK_MONOTONIC, 0, &(s->sample_time), NULL);
+               
        }
        
        // Needed to keep pthreads happy
@@ -299,10 +307,9 @@ void Sensor_EndResponse(FCGIContext * context, Sensor * s, DataFormat format)
  */
 void Sensor_Handler(FCGIContext *context, char * params)
 {
-       struct timeval now;
-       gettimeofday(&now, NULL);
+       struct timespec now;
+       clock_gettime(CLOCK_MONOTONIC, &now);
        double current_time = TIMEVAL_DIFF(now, *Control_GetStartTime());
-
        int id = 0;
        const char * name = "";
        double start_time = 0;
@@ -375,7 +382,7 @@ void Sensor_Handler(FCGIContext *context, char * params)
                        FCGI_RejectJSON(context, "Negative sampling speed!");
                        return;
                }               
-               s->sample_us = 1e6*sample_s;
+               DOUBLE_TO_TIMEVAL(sample_s, &(s->sample_time));
        }
        
        
index bb2fb9e..0188361 100644 (file)
@@ -51,14 +51,19 @@ typedef struct
        InitFn init;
        /** Function to cleanup the sensor **/
        CleanFn cleanup;
+       /** Function to sanity check the sensor readings **/
+       SanityFn sanity;
        /** Human readable name of the sensor **/
        const char * name;
-       /** Thresholds on the sensor **/
-       SensorThreshold thresholds;
        /** Sampling rate **/
-       int sample_us;
-       /** Averages per DataPoint **/
+       struct timespec sample_time;
+       /** Number of averages per sample **/
        int averages;
+       /** Current data **/
+       DataPoint current_data;
+       /** Number of points read so far before applying average **/
+       int num_read;
+
 
        
 } Sensor;
@@ -73,7 +78,6 @@ extern void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg);
 
 extern void * Sensor_Loop(void * args); // Main loop for a thread that handles a Sensor
 extern bool Sensor_Read(Sensor * s, DataPoint * d); // Read a single DataPoint, indicating if it has changed since the last one
-extern void Sensor_CheckData(Sensor * s, double value); // Check a DataPoint
 extern Sensor * Sensor_Identify(const char * str); // Identify a Sensor from a string
 
 extern void Sensor_Handler(FCGIContext *context, char * params); // Handle a FCGI request for Sensor data
index b72201d..a412f7c 100644 (file)
@@ -3,7 +3,7 @@ CXX = gcc
 FLAGS = -std=c99 -Wall -pedantic -g -I../
 #-I/usr/include/opencv -I/usr/include/opencv2/highgui For OpenCV
 LIB = -lpthread
-OBJ = strain.o resource.o piped.o
+OBJ = strain.o resource.o pressure.o
 HEADERS = $(wildcard *.h)
 RM = rm -f
 
diff --git a/server/sensors/pressure.c b/server/sensors/pressure.c
new file mode 100644 (file)
index 0000000..61cdbcf
--- /dev/null
@@ -0,0 +1,104 @@
+/**
+ * @file pressure.c
+ * @purpose Implementation of Pressure reading functions
+ */
+
+#include "pressure.h"
+#include "../bbb_pin.h"
+#include "../log.h" // For Fatal()
+
+#define PSI_TO_KPA 6.89475729
+
+/**
+ * Get the ADC number of a Pressure sensor
+ * @param id - Id of the sensor
+ * @returns ADC as defined in bbb_pin_defines.h
+ */
+static int Pressure_GetADC(int id)
+{
+       switch (id)
+       {
+               case PRES_HIGH0:
+                       return ADC1;
+               case PRES_HIGH1:
+                       return ADC3;
+               case PRES_LOW0:
+                       return ADC5;
+               default:
+                       Fatal("Unknown Pressure id %d", id);
+                       return -1; // Should never happen
+       }
+}
+
+/**
+ * Convert an ADC voltage into a Pressure reading
+ * @param id - Sensor ID
+ * @param adc - ADC reading
+ * @returns Pressure in kPa
+ */
+double Pressure_Callibrate(int id, int adc)
+{
+       //double voltage = ADC_TO_VOLTS(adc); // convert reading to voltage
+
+       switch (id)
+       {
+               case PRES_HIGH0:
+               case PRES_HIGH1:
+               {
+                       static const double Vs = 5e3; // In mVs
+                       static const double Pmin = 0.0 * PSI_TO_KPA;
+                       static const double Pmax = 150.0 * PSI_TO_KPA;
+                       double Vout = ADC_TO_MVOLTS(adc);
+                       return ((Vout - 0.1*Vs)/(0.8*Vs))*(Pmax - Pmin) + Pmin;
+               }       
+               case PRES_LOW0:
+                       return (200.0 * (adc / ADC_RAW_MAX));
+               default:
+                       Fatal("Unknown Pressure id %d", id);
+                       return -1; // Should never happen
+       }
+       
+}
+
+/**
+ * Initialise a Pressure sensor
+ * @param name - Ignored
+ * @param id - The id of the Pressure sensor
+ * @returns true on success, false on error
+ */
+bool Pressure_Init(const char * name, int id)
+{
+       return ADC_Export(Pressure_GetADC(id));
+}
+
+/**
+ * Cleanup a Pressure Sensor
+ * @param id - The id of the sensor to cleanup
+ * @returns true on success, false on failure
+ */
+bool Pressure_Cleanup(int id)
+{
+       ADC_Unexport(Pressure_GetADC(id));
+       return true;
+}
+
+/**
+ * Read a Pressure Sensor
+ * @param id - id of the sensor to read
+ * @param value - Where the value will be stored on a successful read
+ * @returns true on success, false on failure
+ */
+bool Pressure_Read(int id, double * value)
+{
+       //static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+       //pthread_mutex_lock(&mutex);
+       bool result = false;
+       int adc = 0;
+       if (ADC_Read(Pressure_GetADC(id), &adc))
+       {
+               *value = Pressure_Callibrate(id, adc);
+               result = true;
+       }
+       //pthread_mutex_unlock(&mutex);
+       return result;
+}
diff --git a/server/sensors/pressure.h b/server/sensors/pressure.h
new file mode 100644 (file)
index 0000000..22d1adc
--- /dev/null
@@ -0,0 +1,23 @@
+/**
+ * @file pressure.h
+ * @purpose Declarations for Pressure Sensor functions
+ */
+
+#ifndef _PRESSURE_H
+
+#include "../common.h"
+
+typedef enum
+{
+       PRES_HIGH0,
+       PRES_HIGH1,
+       PRES_LOW0
+} PressureId;
+
+extern bool Pressure_Init(const char * name, int id);
+extern bool Pressure_Cleanup(int id);
+extern bool Pressure_Read(int id, double * value);
+
+#endif //_PRESSURE_H
+
+
index fa52562..80c8594 100644 (file)
@@ -17,10 +17,10 @@ bool Resource_Read(int id, double * value)
        switch (id)
        {
                case RESOURCE_CPU_USER:
-                       *value = TIMEVAL_TO_DOUBLE(usage.ru_utime);
+                       *value = usage.ru_utime.tv_sec + 1e-6*usage.ru_utime.tv_usec;
                        break;
                case RESOURCE_CPU_SYS:
-                       *value = TIMEVAL_TO_DOUBLE(usage.ru_stime);
+                       *value = usage.ru_stime.tv_sec + 1e-6*usage.ru_stime.tv_usec;
                        break;
                default:
                        Log(LOGWARN, "Unknown id %d", id);
index 3ee1624..9492de7 100644 (file)
@@ -33,7 +33,7 @@ static int Strain_To_GPIO(StrainID id)
                default:
                        Fatal("Unknown StrainID %d", id);
                        return -1; // Should never happen
-       }
+  }
 }
 
 /**
diff --git a/server/shadow b/server/shadow
new file mode 100644 (file)
index 0000000..49c685d
--- /dev/null
@@ -0,0 +1,3 @@
+admin:$1$Pga9JAbx$FvYbRAxzx6lLl6IGIxMsF./:[email protected]:0
+user1:$1$DY5atz7F$XsLeojV/lL1uiLAkvR8Xm0.:[email protected]:1
+user2:$1$ahKf1yrT$Dksl6fXzVfzJjdc55gkSZ.:[email protected]:1
index ecefd5a..60817cf 100644 (file)
@@ -9,34 +9,51 @@
     <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
     <script type="text/javascript" src="static/jquery.flot.min.js"></script>
     <script type="text/javascript" src="static/mctx.gui.js"></script>
+    <script type="text/javascript" src="static/mctx.control.js"></script>
     
     <link rel="stylesheet" type="text/css" href="static/style.css">
     <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
     <script type="text/javascript">
       runBeforeLoad().done(function () {
         $(document).ready(function () {
-         //Show the content!
-         $("#content").css("display", "block");
-         //Set the welcome bar
-         var name = " " + (mctx.friendlyName ? mctx.friendlyName : "");
-         $("#welcome-container").text("Welcome"+ name + "!");
-         $("#logout-container").css("display", "block");
-         //$("#menu-container").populateNavbar();
-
-         $("#logout").click(function () {
-           $("#logout").logout();
-         });
-
-         $("#main_controls").submit(function () {
-           //Validate!
-           return false;
-         });
-
-         $("#errorlog").setErrorLog();
-         $("#strain-graphs").setStrainGraphs();
+          $("form").submit(function () { //Prevent form submit globally
+            return false;
+          })
+          
+          //Set the status updated
+          $("#state-exp").setStatusUpdater();
+          
+          //Set the logic for the start controls
+          $("#start-controls").submit(function () {
+            var start = $("#start-controls input[type='button']");
+            var force = $("#start-controls input[name='start_force']");
+            
+            $(this).startExperiment(start, $("#experiment_name").val(), 
+                      force.is(":checked"), $("#start-result"));
+            force.prop("checked", false);
+          });
+          
+          //Set the logic for the stop button
+          $("#experiment-stop").click(function () {
+            $(this).stopExperiment($("#stop-status"));
+          });
+          
+          //Set the logic for the pressure controls
+          $("#pressure-controls").submit(function () {
+            var pressure = {
+              set : $("#pressure-set").val(),
+              step : $("#pressure-stepsize").val(),
+              wait : $("#pressure-stepwait").val(),
+              count : $("#pressure-stepcount").val()
+            };   
+            $(this).setPressure(pressure, $("#pressure-result"));
+          });
        });       
-      })
-
+      }).fail(function () {
+        $(document).ready(function () {
+         $("#state-exp").text("Connection failure").parent().addClass("fail");
+       });  
+      });
     </script>
   </head>
   
       </noscript>
 
       <div id="content">
+        <div class="widget" id="sidebar-show">&gt;</div>
         <div id="sidebar">
           <div class="widget">
+            <div id="sidebar-hide">&lt;</div>
             <div class="title">Navigation menu</div>
-            <div class="nav-menu">
-            <ul>
-               <li><a href="index.html"><span>Home</span></a></li>
-               <li><a href="control.html"><span>Experiment control</span></a></li>
-               <li><a href="pintest.html"><span>Pin debugging</span></a></li>
-               <li class="last"><a href="#"><span>Help</span></a></li>
-            </ul>
+            <div id="sidebar-menu" class="nav-menu">
+              <ul>
+                <li><a href="index.html"><span>Home</span></a></li>
+                <li><a href="control.html"><span>Experiment control</span></a></li>
+                <li><a href="graph.html"><span>Experiment graphs</span></a></li>
+                <li><a href="data.html"><span>Experiment data</span></a></li>
+                <li><a href="pintest.html"><span>Pin debugging</span></a></li>
+                <li class="last"><a href="help.html"><span>Help</span></a></li>
+              </ul>
             </div>
           </div>
-          <div class="widget">
-            <div class="title">Help</div>
-
-          </div>
         </div>
         <!-- End sidebar -->
 
         <div id="main">
           <div class="widget">
-            <div class="title">Controls</div>
-            <b>Main controls</b>
-            <form id="main_controls" action="">
+            <div class="title">Experiment Overview</div>
+            <table class="horizontal medium">
+              <tr id="state-exp-r">
+                <th>Experiment state</th>
+                <td id="state-exp"></td>
+                <td style="text-align: right;">
+                  <input id="experiment-stop" type="button" value="Stop">
+                </td>
+              </tr>
+            </table>
+            <div id="stop-status">
+              &nbsp;
+            </div>
+            
+            <div class="sub-title">Error and warning messages</div>
+            <textarea id="errorlog" wrap="off" rows="4" cols="30" readonly>
+            </textarea>
+          </div>
+          
+          <div id="start-widget" class="widget">
+            <div class="title centre">Start an experiment</div>
+            <form id="start-controls" class="nice" action="#">
+              <p>
+                <label for="experiment_name">Experiment name</label>
+                <input id="experiment_name" type="text">
+                
+                <label for="start_force">Overwrite existing</label>
+                <input type="checkbox" name="start_force" id="start_force">
+              </p>
+              <p id="start-result">
+                &nbsp;
+              </p>
+              <p class="centre">
+                <input type="submit" name="start_strain" value="Strain test">
+                <input type="submit" name="start_explode" value="Explode test">                
+              </p>
+            </form>
+          </div>
+          
+          <div id="pressure-widget" class="widget">            
+            <form id="pressure-controls" action="#" class="nice clear">
               <table>
                 <tr>
-                  <td>Experiment name</td>
-                  <td><input name="experiment_name" type="text"></td>
+                  <td><label for="pressure-set">Starting pressure (kPa)</label></td>
+                  <td><label for="pressure-stepsize">Pressure step size (kPa)</label></td>
                 </tr>
                 <tr>
-                  <td>Experiment mode</td>
-                  <td>
-                    <input name="experiment_type" value="strain" type="radio"> Strain it
-                    <input name="experiment_type" value="explode" type="radio"> Explode it
-                  </td>
+                  <td><input id="pressure-set" type="text"></td>
+                  <td><input id="pressure-stepsize" type="text"></td>
                 </tr>
                 <tr>
-                  <td>
-                  </td>
-                  <td align="right">
-                    <input type="submit" value="Start">
-                    <input type="submit" value="Pause">
-                    <input type="submit" value="Stop">
-                  </td>
+                  <td><label for="pressure-stepcount">Number of steps to make</label></td>
+                  <td><label for="pressure-stepwait">Wait time between steps (s)</label></td>
+                </tr>
+                <tr>
+                  <td><input id="pressure-stepcount" type="text"></td>
+                  <td><input id="pressure-stepwait" type="text"></td>
                 </tr>
               </table>
+              <p class="left" id="pressure-result">
+                
+              </p>
+              <p class="right">
+                <input type="submit" value="Set pressure">
+              </p>
             </form>
-            <b>Error and warning messages</b><br>
-            <textarea id="errorlog" wrap="off" rows="4" cols="30" readonly></textarea>
           </div>
-          <div class="widget">
-            <div class="title">Strain gauges</div>
-            <div id="strain-graphs" class="graph">
-              <!-- Strain graph placeholder -->
-            </div>
+          
+          <div id="stats-widget" class="widget">
+            <form id="stats" action="#" class="nice clear">
+              <table>
+                <tr>
+                  <td><label for="stats-mainspressure">Mains pressure (kPa)</label></td>
+                  <td><label for="stats-canpressure">Can pressure (kPa)</label></td>
+                </tr>
+                <tr>
+                  <td><input id="stats-mainspressure" type="text" readonly></td>
+                  <td><input id="stats-canpressure" type="text" readonly></td>
+                </tr>
+                <tr class="stats-strain">
+                  <td><label for="stats-strain1">Central hoop strain</label></td>
+                  <td><label for="stats-strain2">Central longitudinal strain</label></td>
+                </tr>
+                <tr class="stats-strain">
+                  <td><input id="stats-strain1" type="text" readonly></td>
+                  <td><input id="stats-strain2" type="text" readonly></td>
+                </tr>
+                <tr class="stats-strain">
+                  <td><label for="stats-strain3">End hoop strain</label></td>
+                  <td><label for="stats-strain4">End longitudinal strain</label></td>
+                </tr>
+                <tr class="stats-strain">
+                  <td><input id="stats-strain3" type="text" readonly></td>
+                  <td><input id="stats-strain4" type="text" readonly></td>
+                </tr>
+                <tr class="stats-strain">
+                  <td><label for="stats-dilatometer">Dilatometer reading</label></td>
+                  <td><label for="stats-dilatometer">Camera feed</label></td>
+                </tr>
+                <tr class="stats-strain">
+                  <td><input id="stats-dilatometer" type="text" readonly></td>
+                  <td><a href="#">Link</a></td>
+                </tr>
+              </table>
+            </form>
           </div>
+          
         </div>
         <!-- End main content -->
       </div>
diff --git a/testing/MCTXWeb/public_html/data-Justin.html b/testing/MCTXWeb/public_html/data-Justin.html
deleted file mode 100644 (file)
index 975b538..0000000
+++ /dev/null
@@ -1,271 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-
-<!--Current Experiment section is pretty much working, still working out the previous experiments bit-->
-<!--Basically, the download links and graph links automatically update depending on what is selected in the drop-down menus-->
-<!--Some dodgy javascript is involved but it sort of works-->
-<!--The HTML5 "download" attribute is used to force downloads, but that's not compatible with IE (it just views the files in IE)-->
-<html>
-  <head>
-    <title>MCTX3420 Web Interface</title>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-    <!--[if lte IE 8]>
-      <script language="javascript" type="text/javascript" src="static/excanvas.min.js"></script>
-    <![endif]-->
-    <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
-    <script type="text/javascript" src="static/jquery.flot.min.js"></script>
-    <script type="text/javascript" src="static/mctx.gui.js"></script>
-
-    <link rel="stylesheet" type="text/css" href="static/style.css">
-    <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
-    <script type="text/javascript">
-      runBeforeLoad().done(function() {
-        $(document).ready(function() {
-          //Show the content!
-          $("#content").css("display", "block");
-          //Set the welcome bar
-          var name = " " + (mctx.friendlyName ? mctx.friendlyName : "");
-          $("#welcome-container").text("Welcome" + name + "!");
-          $("#logout-container").css("display", "block");
-          //$("#menu-container").populateNavbar();
-
-          $("#logout").click(function() {
-            $("#logout").logout();
-          });
-
-          $("#main_controls").submit(function() {
-            //Validate!
-            return false;
-          });
-
-          $("#errorlog").setErrorLog();
-        });
-      })
-
-      //function to load appropriate graph image depending on drop-down menu
-      function graphLoad(graphid, folder, newgraph) {
-        document.getElementById(graphid).src = folder + "/" + newgraph + ".png";
-      }
-      //function to load appropriate graph image depending on drop-down menu
-      function graphLoad2(graphid, newgraph) {
-        var exp = document.getElementById("expselect").value;
-        document.getElementById(graphid).src = exp + "/" + newgraph + ".png";
-      }
-      //function to create appropriate experiment links depending on drop-down menu
-      function expLoad(expclass, folder, newlink) {
-        var elems = document.getElementsByClassName(expclass);
-        for (i = 0; i < elems.length; i++) {
-          var elem = elems[i];
-          elem.href = folder + "/" + newlink;
-        }
-      }
-      //function to update experiment links links depending on drop-down menu
-      function expLoadTotal(folder) {
-        expLoad('s1', folder, 'strain1');
-        expLoad('s2', folder, 'strain2');
-        expLoad('s3', folder, 'strain3');
-        expLoad('s4', folder, 'strain4');
-        expLoad('p1', folder, 'pressure1');
-        expLoad('p2', folder, 'pressure2');
-        expLoad('d', folder, 'all.zip');
-      }
-    </script>
-  </head>
-
-  <body>
-    <div id="header-wrap">
-      <div id="header">
-        <div id="leftnav">
-          <a href="http://www.uwa.edu.au/" target="_blank">
-            <img alt = "The University of Western Australia"
-                 src="static/uwacrest-text.png">
-          </a>
-          <span id="title">Exploding Cans</span>
-        </div>
-        <div id="rightnav">
-          <span id="welcome-container">
-          </span>
-          <span id="date">
-            <script type="text/javascript">getDate();</script>
-          </span>
-          <div id="logout-container">
-            <form action="#">
-              <div>
-                <input type="button" id="logout" value="Logout">
-              </div>
-            </form>
-          </div>
-        </div>
-        <div class="clear"></div>
-      </div>
-    </div>
-    <!-- End header -->
-
-    <div id="content-wrap">
-      <noscript>
-        <div class="widget centre">
-          <div class="title">JavaScript required</div>
-          This website requires JavaScript to function correctly.
-          Please enable JavaScript to use this site.
-        </div>
-      </noscript>
-
-      <div id="content">
-        <div id="sidebar">
-          <div class="widget">
-            <div class="title">Navigation menu</div>
-            <div class="nav-menu">
-              <ul>
-                <li><a href="index.html"><span>Home</span></a></li>
-                <li><a href="control.html"><span>Experiment control</span></a></li>
-                <li><a href="pintest.html"><span>Pin debugging</span></a></li>
-                <li class="last"><a href="#"><span>Help</span></a></li>
-              </ul>
-            </div>
-          </div>
-          <div class="widget">
-            <div class="title">Help</div>
-
-          </div>
-        </div>
-        <!-- End sidebar -->
-
-        <div id="main">
-          <div class="widget">
-            <div class="title">Experiment Data</div>
-            <b>Current Experiment</b>
-            <table>
-              <tr>
-                <!--I have no idea how we'll end up storing all the experiment data on the Beaglebone so these currently just link to a bunch of dummy files-->
-                <td>View Sensor Data</td>
-                <td></td>
-                <td><a href="current/strain1">Strain 1</a></td>
-                <td><a href="current/strain2">Strain 2</a></td>
-                <td><a href="current/strain3">Strain 3</a></td>
-                <td><a href="current/strain4">Strain 4</a></td>
-                <td><a href="current/pressure1">Pressure 1</a></td>
-                <td><a href="current/pressure2">Pressure 2</a></td>
-              </tr>
-              <tr>
-                <!--uses the download attribute to force downloads, only compatible with some browsers. Other users will just have to right-click and Save File As-->
-                <td>Save Sensor Data</td>
-                <td><a href="current/all.zip" download><input type="button" value="Download All"></a></td>
-                <td><a href="current/strain1" download><input type="button" value="Strain 1"></a></td>
-                <td><a href="current/strain2" download><input type="button" value="Strain 2"></a></td>
-                <td><a href="current/strain3" download><input type="button" value="Strain 3"></a></td>
-                <td><a href="current/strain4" download><input type="button" value="Strain 4"></a></td>
-                <td><a href="current/pressure1" download><input type="button" value="Pressure 1"></a></td>
-                <td><a href="current/pressure1" download><input type="button" value="Pressure 1"></a></td>
-              </tr>
-              <tr>
-                <!--not sure about how exactly we'll be linking to dilatometer or camera data yet-->
-                <td>Dilatometer</td>
-                <td><a href="nowhere" download="nowhere"><input type="button" value="Download Data"></a></td>
-                <td><a href="nowhere">View Data</a></td>
-              </tr>
-              <tr>
-                <td>Camera Data</td>
-                <td><a href="nowhere" download="nowhere"><input type="button" value="Download Data"></a></td>
-                <td><a href="nowhere">View Data</a></td>
-              </tr>
-              <tr>
-
-                <td>Graphs</td>
-                <td>
-                  <form>
-                    <select onChange="graphLoad('g1', 'current', this.value)">
-                      <!--link this to graphs functionality, currently just loads a placeholder image-->
-                      <!--graphs could just plot the data over entire time range of the experiment-->
-                      <option value="nograph">Choose to view</option>
-                      <option value="graph1">Strain 1</option>
-                      <option value="graph2">Strain 2</option>
-                      <option value="graph3">Strain 3</option>
-                      <option value="graph4">Strain 4</option>
-                      <option value="graph5">Pressure 1</option>
-                      <option value="graph6">Pressure 2</option>
-                    </select>
-                  </form>
-
-                </td>
-              </tr>
-            </table>
-            <img src="current/nograph.png" id="g1">
-            <p><b>Previous Experiments</b></p>
-            <table>
-              <tr>
-                <td>Select Experiment</td>
-                <td>
-                  <!--Upon selecting experiment, modify all the download links-->
-                  <!--TODO: prevent downloads of data if no experiment is selected-->
-                  <select id="expselect" onChange="expLoadTotal(this.value)">
-                    <option value="current">Current</option>
-                    <option value="exp1">Experiment 1</option>
-                    <option value="exp2">Experiment 2</option>
-                    <option value="exp3">Experiment 3</option>
-                    <option value="exp4">Experiment 4</option>
-                    <option value="exp5">Experiment 5</option>
-                  </select>
-                </td>
-              </tr>
-              <tr>
-                <td>Experiment Date</td>
-                <!--Lookup the experiment date here for identification purposes-->
-                <td><b>DATE LOOKUP</b></td>
-              </tr>
-              <tr>
-                <td>View Sensor Data</td>
-                <td></td>
-                <td><a href="current/strain1" class="s1">Strain 1</a></td>
-                <td><a href="current/strain2" class="s2">Strain 2</a></td>
-                <td><a href="current/strain3" class="s3">Strain 3</a></td>
-                <td><a href="current/strain4" class="s4">Strain 4</a></td>
-                <td><a href="current/pressure1" class="p1">Pressure 1</a></td>
-                <td><a href="current/pressure2" class="p2">Pressure 2</a></td>
-              </tr>
-              <tr>
-                <td>Save Sensor Data</td>
-                <td><a href="current/all.zip" download class="d"><input type="button" value="Download All"></a></td>
-                <td><a href="current/strain1" download class="s1"><input type="button" value="Strain 1"></a></td>
-                <td><a href="current/strain2" download class="s2"><input type="button" value="Strain 2"></a></td>
-                <td><a href="current/strain3" download class="s3"><input type="button" value="Strain 3"></a></td>
-                <td><a href="current/strain4" download class="s4"><input type="button" value="Strain 4"></a></td>
-                <td><a href="current/pressure1" download class="p1"><input type="button" value="Pressure 1"></a></td>
-                <td><a href="current/pressure2" download class="p2"><input type="button" value="Pressure 1"></a></td>
-              </tr>
-              <tr>
-                <!--not sure about how exactly we'll be linking to dilatometer or camera data yet-->
-                <td>Dilatometer</td>
-                <td><a href="nowhere" download="nowhere"><input type="button" value="Download Data"></a></td>
-                <td><a href="nowhere">View Data</a></td>
-              </tr>
-              <tr>
-                <td>Camera Data</td>
-                <td><a href="nowhere" download="nowhere"><input type="button" value="Download Data"></a></td>
-                <td><a href="nowhere">View Data</a></td>
-              </tr>
-              <tr>
-                <td>Graphs</td>
-                <td>
-                  <form>
-                    <select onChange="graphLoad2('g2', this.value)">
-                      <!--link this to graphs functionality, currently just loads a placeholder image-->
-                      <!--graphs could just plot the data over entire time range of the experiment-->
-                      <option value="nograph">Choose to view</option>
-                      <option value="graph1">Strain 1</option>
-                      <option value="graph2">Strain 2</option>
-                      <option value="graph3">Strain 3</option>
-                      <option value="graph4">Strain 4</option>
-                      <option value="graph5">Pressure 1</option>
-                      <option value="graph6">Pressure 2</option>
-                    </select>
-                  </form>
-                </td>
-              </tr>
-            </table>
-            <img src="current/nograph.png" id="g2">
-          </div>
-        </div>
-        <!-- End main content -->
-      </div>
-    </div>
-  </body>
-</html>
diff --git a/testing/MCTXWeb/public_html/data.html b/testing/MCTXWeb/public_html/data.html
new file mode 100644 (file)
index 0000000..c4c2807
--- /dev/null
@@ -0,0 +1,253 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+
+<!--Current Experiment section is pretty much working, still working out the previous experiments bit-->
+<!--Basically, the download links and graph links automatically update depending on what is selected in the drop-down menus-->
+<!--Some dodgy javascript is involved but it sort of works-->
+<!--The HTML5 "download" attribute is used to force downloads, but that's not compatible with IE (it just views the files in IE)-->
+<html>
+  <head>
+    <title>MCTX3420 Web Interface</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <!--[if lte IE 8]>
+      <script language="javascript" type="text/javascript" src="static/excanvas.min.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
+    <script type="text/javascript" src="static/jquery.flot.min.js"></script>
+    <script type="text/javascript" src="static/mctx.gui.js"></script>
+
+    <link rel="stylesheet" type="text/css" href="static/style.css">
+    <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
+    <script type="text/javascript">
+      runBeforeLoad().done(function() {
+        $(document).ready(function() {
+        });
+      })
+
+      //function to load appropriate graph image depending on drop-down menu
+      function graphLoad(graphid, folder, newgraph) {
+        document.getElementById(graphid).src = folder + "/" + newgraph + ".png";
+      }
+      //function to load appropriate graph image depending on drop-down menu
+      function graphLoad2(graphid, newgraph) {
+        var exp = document.getElementById("expselect").value;
+        document.getElementById(graphid).src = exp + "/" + newgraph + ".png";
+      }
+      //function to create appropriate experiment links depending on drop-down menu
+      function expLoad(expclass, folder, newlink) {
+        var elems = document.getElementsByClassName(expclass);
+        for (i = 0; i < elems.length; i++) {
+          var elem = elems[i];
+          elem.href = folder + "/" + newlink;
+        }
+      }
+      //function to update experiment links links depending on drop-down menu
+      function expLoadTotal(folder) {
+        expLoad('s1', folder, 'strain1');
+        expLoad('s2', folder, 'strain2');
+        expLoad('s3', folder, 'strain3');
+        expLoad('s4', folder, 'strain4');
+        expLoad('p1', folder, 'pressure1');
+        expLoad('p2', folder, 'pressure2');
+        expLoad('d', folder, 'all.zip');
+      }
+    </script>
+  </head>
+
+  <body>
+    <div id="header-wrap">
+      <div id="header">
+        <div id="leftnav">
+          <a href="http://www.uwa.edu.au/" target="_blank">
+            <img alt = "The University of Western Australia"
+                 src="static/uwacrest-text.png">
+          </a>
+          <span id="title">Exploding Cans</span>
+        </div>
+        <div id="rightnav">
+          <span id="welcome-container">
+          </span>
+          <span id="date">
+            <script type="text/javascript">getDate();</script>
+          </span>
+          <div id="logout-container">
+            <form action="#">
+              <div>
+                <input type="button" id="logout" value="Logout">
+              </div>
+            </form>
+          </div>
+        </div>
+        <div class="clear"></div>
+      </div>
+    </div>
+    <!-- End header -->
+
+    <div id="content-wrap">
+      <noscript>
+        <div class="widget centre">
+          <div class="title">JavaScript required</div>
+          This website requires JavaScript to function correctly.
+          Please enable JavaScript to use this site.
+        </div>
+      </noscript>
+
+      <div id="content">
+        <div class="widget" id="sidebar-show">&gt;</div>
+        <div id="sidebar">
+          <div class="widget">
+            <div id="sidebar-hide">&lt;</div>
+            <div class="title">Navigation menu</div>
+            <div id="sidebar-menu" class="nav-menu">
+              <ul>
+                <li><a href="index.html"><span>Home</span></a></li>
+                <li><a href="control.html"><span>Experiment control</span></a></li>
+                <li><a href="graph.html"><span>Experiment graphs</span></a></li>
+                <li><a href="data.html"><span>Experiment data</span></a></li>
+                <li><a href="pintest.html"><span>Pin debugging</span></a></li>
+                <li class="last"><a href="help.html"><span>Help</span></a></li>
+              </ul>
+            </div>
+          </div>
+        </div>
+        <!-- End sidebar -->
+
+        <div id="main">
+          <div class="widget">
+            <div class="title">Experiment Data</div>
+            <b>Current Experiment</b>
+            <table>
+              <tr>
+                <!--I have no idea how we'll end up storing all the experiment data on the Beaglebone so these currently just link to a bunch of dummy files-->
+                <td>View Sensor Data</td>
+                <td></td>
+                <td><a href="current/strain1">Strain 1</a></td>
+                <td><a href="current/strain2">Strain 2</a></td>
+                <td><a href="current/strain3">Strain 3</a></td>
+                <td><a href="current/strain4">Strain 4</a></td>
+                <td><a href="current/pressure1">Pressure 1</a></td>
+                <td><a href="current/pressure2">Pressure 2</a></td>
+              </tr>
+              <tr>
+                <!--uses the download attribute to force downloads, only compatible with some browsers. Other users will just have to right-click and Save File As-->
+                <td>Save Sensor Data</td>
+                <td><a href="current/all.zip" download><input type="button" value="Download All"></a></td>
+                <td><a href="current/strain1" download><input type="button" value="Strain 1"></a></td>
+                <td><a href="current/strain2" download><input type="button" value="Strain 2"></a></td>
+                <td><a href="current/strain3" download><input type="button" value="Strain 3"></a></td>
+                <td><a href="current/strain4" download><input type="button" value="Strain 4"></a></td>
+                <td><a href="current/pressure1" download><input type="button" value="Pressure 1"></a></td>
+                <td><a href="current/pressure1" download><input type="button" value="Pressure 1"></a></td>
+              </tr>
+              <tr>
+                <!--not sure about how exactly we'll be linking to dilatometer or camera data yet-->
+                <td>Dilatometer</td>
+                <td><a href="nowhere" download="nowhere"><input type="button" value="Download Data"></a></td>
+                <td><a href="nowhere">View Data</a></td>
+              </tr>
+              <tr>
+                <td>Camera Data</td>
+                <td><a href="nowhere" download="nowhere"><input type="button" value="Download Data"></a></td>
+                <td><a href="nowhere">View Data</a></td>
+              </tr>
+              <tr>
+
+                <td>Graphs</td>
+                <td>
+                  <form>
+                    <select onChange="graphLoad('g1', 'current', this.value)">
+                      <!--link this to graphs functionality, currently just loads a placeholder image-->
+                      <!--graphs could just plot the data over entire time range of the experiment-->
+                      <option value="nograph">Choose to view</option>
+                      <option value="graph1">Strain 1</option>
+                      <option value="graph2">Strain 2</option>
+                      <option value="graph3">Strain 3</option>
+                      <option value="graph4">Strain 4</option>
+                      <option value="graph5">Pressure 1</option>
+                      <option value="graph6">Pressure 2</option>
+                    </select>
+                  </form>
+
+                </td>
+              </tr>
+            </table>
+            <img src="current/nograph.png" id="g1">
+            <p><b>Previous Experiments</b></p>
+            <table>
+              <tr>
+                <td>Select Experiment</td>
+                <td>
+                  <!--Upon selecting experiment, modify all the download links-->
+                  <!--TODO: prevent downloads of data if no experiment is selected-->
+                  <select id="expselect" onChange="expLoadTotal(this.value)">
+                    <option value="current">Current</option>
+                    <option value="exp1">Experiment 1</option>
+                    <option value="exp2">Experiment 2</option>
+                    <option value="exp3">Experiment 3</option>
+                    <option value="exp4">Experiment 4</option>
+                    <option value="exp5">Experiment 5</option>
+                  </select>
+                </td>
+              </tr>
+              <tr>
+                <td>Experiment Date</td>
+                <!--Lookup the experiment date here for identification purposes-->
+                <td><b>DATE LOOKUP</b></td>
+              </tr>
+              <tr>
+                <td>View Sensor Data</td>
+                <td></td>
+                <td><a href="current/strain1" class="s1">Strain 1</a></td>
+                <td><a href="current/strain2" class="s2">Strain 2</a></td>
+                <td><a href="current/strain3" class="s3">Strain 3</a></td>
+                <td><a href="current/strain4" class="s4">Strain 4</a></td>
+                <td><a href="current/pressure1" class="p1">Pressure 1</a></td>
+                <td><a href="current/pressure2" class="p2">Pressure 2</a></td>
+              </tr>
+              <tr>
+                <td>Save Sensor Data</td>
+                <td><a href="current/all.zip" download class="d"><input type="button" value="Download All"></a></td>
+                <td><a href="current/strain1" download class="s1"><input type="button" value="Strain 1"></a></td>
+                <td><a href="current/strain2" download class="s2"><input type="button" value="Strain 2"></a></td>
+                <td><a href="current/strain3" download class="s3"><input type="button" value="Strain 3"></a></td>
+                <td><a href="current/strain4" download class="s4"><input type="button" value="Strain 4"></a></td>
+                <td><a href="current/pressure1" download class="p1"><input type="button" value="Pressure 1"></a></td>
+                <td><a href="current/pressure2" download class="p2"><input type="button" value="Pressure 1"></a></td>
+              </tr>
+              <tr>
+                <!--not sure about how exactly we'll be linking to dilatometer or camera data yet-->
+                <td>Dilatometer</td>
+                <td><a href="nowhere" download="nowhere"><input type="button" value="Download Data"></a></td>
+                <td><a href="nowhere">View Data</a></td>
+              </tr>
+              <tr>
+                <td>Camera Data</td>
+                <td><a href="nowhere" download="nowhere"><input type="button" value="Download Data"></a></td>
+                <td><a href="nowhere">View Data</a></td>
+              </tr>
+              <tr>
+                <td>Graphs</td>
+                <td>
+                  <form>
+                    <select onChange="graphLoad2('g2', this.value)">
+                      <!--link this to graphs functionality, currently just loads a placeholder image-->
+                      <!--graphs could just plot the data over entire time range of the experiment-->
+                      <option value="nograph">Choose to view</option>
+                      <option value="graph1">Strain 1</option>
+                      <option value="graph2">Strain 2</option>
+                      <option value="graph3">Strain 3</option>
+                      <option value="graph4">Strain 4</option>
+                      <option value="graph5">Pressure 1</option>
+                      <option value="graph6">Pressure 2</option>
+                    </select>
+                  </form>
+                </td>
+              </tr>
+            </table>
+            <img src="current/nograph.png" id="g2">
+          </div>
+        </div>
+        <!-- End main content -->
+      </div>
+    </div>
+  </body>
+</html>
index c15d855..b7d87f1 100644 (file)
     <title>MCTX3420 Web Interface</title>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
     <!--[if lte IE 8]>
-      <script language="javascript" type="text/javascript" src="static/excanvas.min.js"></script>
+    <script language="javascript" type="text/javascript" src="static/excanvas.min.js"></script>
     <![endif]-->
     <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
     <script type="text/javascript" src="static/jquery.flot.min.js"></script>
+
     <script type="text/javascript" src="static/mctx.gui.js"></script>
-        <script type="text/javascript" src="static/mctx.graph.js"></script>
-    
+    <script type="text/javascript" src="static/mctx.graph.js"></script>
     <link rel="stylesheet" type="text/css" href="static/style.css">
     <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
     <script type="text/javascript">
-      runBeforeLoad().always(function () {
-        $(document).ready(function () {
+      runBeforeLoad().always(function() {
+        $(document).ready(function() {
           $("#graph-controls").setDevices();
-          $("#graph-run").runButton();
-          
-       });       
-      })
+        });
+      });
 
     </script>
   </head>
-  
+
   <body>
-    <div id="header-wrap"></div>
+    <div id="header-wrap">
+      <div id="header">
+        <div id="leftnav">
+          <a href="http://www.uwa.edu.au/" target="_blank">
+            <img alt = "The University of Western Australia"
+                 src="static/uwacrest-text.png">
+          </a>
+          <span id="title">Exploding Cans</span>
+        </div>
+        <div id="rightnav">
+          <span id="welcome-container">
+          </span>
+          <span id="date">
+            <script type="text/javascript">getDate();</script>
+          </span>
+          <div id="logout-container">
+            <form action="#">
+              <div>
+                <input type="button" id="logout" value="Logout">
+              </div>
+            </form>
+          </div>
+        </div>
+        <div class="clear"></div>
+      </div>
+    </div>
+    <!-- End header -->
+
     <div id="content-wrap">
+      <noscript>
+        <div class="widget centre">
+          <div class="title">JavaScript required</div>
+          This website requires JavaScript to function correctly.
+          Please enable JavaScript to use this site.
+        </div>
+      </noscript>
+
       <div id="content">
+        <div class="widget" id="sidebar-show">&gt;</div>
+        <div id="sidebar">
+          <div class="widget">
+            <div id="sidebar-hide">&lt;</div>
+            <div class="title">Navigation menu</div>
+            <div id="sidebar-menu" class="nav-menu">
+              <ul>
+                <li><a href="index.html"><span>Home</span></a></li>
+                <li><a href="control.html"><span>Experiment control</span></a></li>
+                <li><a href="graph.html"><span>Experiment graphs</span></a></li>
+                <li><a href="data.html"><span>Experiment data</span></a></li>
+                <li><a href="pintest.html"><span>Pin debugging</span></a></li>
+                <li class="last"><a href="help.html"><span>Help</span></a></li>
+              </ul>
+            </div>
+          </div>
+        </div>
+        <!-- End sidebar -->
+
         <div id="main">
           <div class="widget">
             <div class="title">Graph</div>
-              <div id="graph" class="plot">
-                <!-- graph placeholder -->
-              </div>
+            <!-- graph placeholder -->
+            <div id="graph" class="plot"></div>
           </div>
           <div class="widget" id="graph-controls">
-              <!--<div class="title">Visualise</div>-->
-              <b>X-Axis</b>
-                <form id="xaxis" onChange=$("#graph").setGraph()> <input type="radio" name="xaxis" value="time" id="time" checked="yes">time</input> </form>
-                <b>Y-Axis</b>
-                <form id="yaxis" onChange=$("#graph").setGraph()>  </form>
-                <!--b>Right Y-Axis</b>
-                <form id="y2axis" onChange=$("#graph").setGraph()> <input type="radio" name="y2axis" value="none" id="none" checked="yes">none</input></form>-->
+            <!--<div class="title">Visualise</div>-->
+            <b>X-Axis</b>
+            <form id="xaxis" class="change">
+              <input type="radio" name="xaxis" alt="time" id="time" checked="checked">
+              <label for="time">time</label>
+            </form>
+            <b>Y-Axis</b>
+            <form id="yaxis" class="change">  </form>
+            <!--b>Right Y-Axis</b>
+            <form id="y2axis" onChange=$("#graph").setGraph()> <input type="radio" name="y2axis" value="none" id="none" checked="yes">none</form>-->
 
-                <div> 
-                  <form id="time_range" onChange=$("#graph").setGraph()>
-                    <p> 
-                        Time of Last Update <input type="text" value="" id="current_time" disabled></input>
-                        Start Time <input type="text" value="" id="start_time"></input> 
-                        End Time <input type="text" value="" id="end_time"></input>
-                    </p>
-                </form>
-                </div>
-                <input type="button" value="Run" id="graph-run" onClick=$("#graph-run").runButton()></input>
-                <input type="button" value="Open New Graph" disabled></input>
-                <input type="button" value="Save Graph Image" disabled></input>
-                <input type="button" value="Dump Raw Data" disabled></input>
-            </div>
-        
-        <!-- TODO: Put this on its own page? 
-          <div class="widget">
-              <div class="title">Controls</div>
-              <form id="controls">
-                <p>Mode: <input type="radio" name="mode" value="normal">Normal</input>
-                      <input type="radio" name="mode" value="explode">EXPLODE!</input> </p>
-                Filename: <input type="text"> </input>
-                <input type="button" value="Stop" id="stopstart"></input>
-                <input type="button" value="Pause" id="runpause"></input>
-                <input type="button" value="Load"></input>
-               --> 
-                
+            <div> 
+              <form id="time_range" class="change">
+                <p> 
+                  Time of Last Update <input type="text" value="" id="current_time" disabled>
+                  Start Time <input type="text" value="" id="start_time"> 
+                  End Time <input type="text" value="" id="end_time">
+                </p>
               </form>
+            </div>
+            <input type="button" value="Run" id="graph-run" onClick="$('#graph-run').runButton()">
+            <input type="button" value="Open New Graph" disabled>
+            <input type="button" value="Save Graph Image" id="saveimage">
+            <input type="button" value="Dump Raw Data" disabled>
+            <script type="text/javascript">
+              $("#saveimage").click(function() {
+                $("canvas").each(function() {
+                  var image = new Image();
+
+                  window.open(this.toDataURL("image/png"));
+                  $("#graph-controls").append(image);
+                });
+              });
+            </script>
+            
+            <div id="status-text">&nbsp;</div>
           </div>
+          
+
+          <!-- TODO: Put this on its own page? 
+            <div class="widget">
+                <div class="title">Controls</div>
+                <form id="controls">
+                  <p>Mode: <input type="radio" name="mode" value="normal">Normal
+                        <input type="radio" name="mode" value="explode">EXPLODE! </p>
+                  Filename: <input type="text"> 
+                  <input type="button" value="Stop" id="stopstart">
+                  <input type="button" value="Pause" id="runpause">
+                  <input type="button" value="Load">
+                </form>
+            </div>
+          -->
         </div>
       </div>
+      <!-- End main content -->
     </div>
   </body>
-</html>
+</html>
\ No newline at end of file
diff --git a/testing/MCTXWeb/public_html/help-Justin.html b/testing/MCTXWeb/public_html/help-Justin.html
deleted file mode 100644 (file)
index acf23a9..0000000
+++ /dev/null
@@ -1,186 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<html>
-  <head>
-    <title>MCTX3420 Web Interface</title>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-    <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
-    <script type="text/javascript" src="static/mctx.gui.js"></script>
-
-    <link rel="stylesheet" type="text/css" href="static/style.css">
-    <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
-    <script type="text/javascript">
-      runBeforeLoad().done(function() {
-        $(document).ready(function() {
-          //Show the content!
-          $("#content").css("display", "block");
-          //Set the welcome bar
-          var name = " " + (mctx.friendlyName ? mctx.friendlyName : "");
-          $("#welcome-container").text("Welcome" + name + "!");
-          $("#logout-container").css("display", "block");
-          //$("#menu-container").populateNavbar();
-
-          $("#logout").click(function() {
-            $("#logout").logout();
-          });
-
-          $("#main_controls").submit(function() {
-            //Validate!
-            return false;
-          });
-
-          $("#errorlog").setErrorLog();
-        });
-      })
-
-    </script>
-  </head>
-
-  <body>
-    <div id="header-wrap">
-      <div id="header">
-        <div id="leftnav">
-          <a href="http://www.uwa.edu.au/" target="_blank">
-            <img alt = "The University of Western Australia"
-                 src="static/uwacrest-text.png">
-          </a>
-          <span id="title">Exploding Cans</span>
-        </div>
-        <div id="rightnav">
-          <span id="welcome-container">
-          </span>
-          <span id="date">
-            <script type="text/javascript">getDate();</script>
-          </span>
-          <div id="logout-container">
-            <form action="#">
-              <div>
-                <input type="button" id="logout" value="Logout">
-              </div>
-            </form>
-          </div>
-        </div>
-        <div class="clear"></div>
-      </div>
-    </div>
-    <!-- End header -->
-
-    <div id="content-wrap">
-      <div id="content">
-        <div id="sidebar">
-          <div class="widget">
-            <div class="title">Navigation menu</div>
-            <div class="nav-menu">
-              <ul>
-                <li><a href="index.html"><span>Home</span></a></li>
-                <li><a href="control.html"><span>Experiment control</span></a></li>
-                <li><a href="pintest.html"><span>Pin debugging</span></a></li>
-                <li class="last"><a href="#"><span>Help</span></a></li>
-              </ul>
-            </div>
-          </div>
-          <div class="widget justify">
-            <div class="title">Tutorials</div>
-            <p class="justify">For general information on using the MCTX project web interface (i.e. this webpage!) see the <b>'Tutorials'</b> section.
-              This will guide you through setting up, running and analysing an experiment with basic tutorials and explanation of the possible options.</p>
-          </div>
-          <div class="widget justify">
-            <div class="title">Experiment Hardware</div>
-            <p class="justify">For detailed technical information about the experiment hardware, including system diagrams, datasheets and run-downs of the components,
-              see the <b>'Hardware'</b> section.</p>
-          </div>
-          <div class="widget justify">
-            <div class="title">Experiment Software</div>
-            <p class="justify">For detailed technical information about the experiment software, including explanations of its structure, functions and so on, see the
-              <b>'Software'</b> section. Actual code can be accessed on the project's GitHub: <a href="https://github.com/szmoore/MCTX3420">MCTX3420</a>.</p>
-          </div>
-          <div class="widget justify">
-            <div class="title">Contact</div>
-            <p class="justify">If you would like more information about the project or require additional assistance, please contact:</p>
-            <p><b>Adrian Keating</b></p>
-            <p><b>Adam Wittek</b></p>
-          </div>
-        </div>
-        <!-- End sidebar -->
-
-        <div id="main">
-          <div class="widget">
-            <div class="title">Getting Started</div>
-            <p class="justify">Welcome to the MCTX3420 project documentation! On this page, you will find help on how to use the project software,
-              as well as information about the various hardware components.</p>
-            <p class="justify">For a basic introduction to using the experiment, see <b>'Tutorials'</b>.
-              <a href="https://github.com/szmoore/MCTX3420/wiki/System-Overview" target="_blank">System overview</a> provides a general overview of the experiment's hardware, while
-              <a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Web-Interface" target="_blank">Using the web interface</a> provides a tutorial on using the the project software.
-              For detailed information about the experiment hardware, see the <b>'Hardware'</b> section. For detailed information about the project software,
-              see the <b>'Software'</b> section.</p>
-            <div class="sub-title">Tutorials</div>
-            <ul>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Overview" target="_blank">System overview</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Web-Interface" target="_blank">Using the web interface</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Run-an-Experiment" target="_blank">Running an experiment</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-View-Old-Experiments" target="_blank">Viewing previous experiments</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Data-Processing" target="_blank">Data processing</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Safety" target="_blank">System safety</a></li>
-            </ul>
-            <div class="sub-title">Hardware</div>
-            <ul>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Overview" target="_blank">System overview</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Electronics" target="_blank">Electronics</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-BeagleBone" target="_blank">BeagleBone</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Pneumatics" target="_blank">Pneumatics</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Sensors" target="_blank">Sensors</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Mounting" target="_blank">Mounting</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Case" target="_blank">Case</a></li>
-            </ul>
-            <div class="sub-title">Software</div>
-            <ul>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Overview" target="_blank">Software overview</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Browser-Interface" target="_blank">Browser interface</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Server-API" target="_blank">Server API</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Server-Code" target="_blank">Server code</a>
-                <ul>
-                  <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Data-Handling" target="_blank">Data handling</a></li>
-                  <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Sensors-&amp;-Actuators" target="_blank">Sensors and actuators</a></li>
-                  <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Pin-Access" target="_blank">Pin access</a></li>
-                  <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Image-Processing" target="_blank">Image processing</a></li>
-                </ul>
-              </li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Terminology" target="_blank">Terminology</a></li>
-            </ul>
-          </div>
-
-          <div class="widget">
-            <div class="title">Project Wiki</div>
-            <p>All of the project's help documentation is stored in its <a href="https://github.com/szmoore/MCTX3420/wiki" target="_blank">GitHub wiki.</a> This git repository also
-              stores all of the software code for the project; to access the project code files, visit <a href="https://github.com/szmoore/MCTX3420" target="_blank">this link</a>.</p>
-            <p>The following is a list of pages on the GitHub wiki (all of which can be accessed via the categories above):<p>
-            <ul>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Data-Processing" target="_blank">Getting Started: Data Processing</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Run-an-Experiment" target="_blank">Getting Started: Run an Experiment</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-View-Old-Experiments" target="_blank">Getting Started: View Old Experiments</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Web-Interface" target="_blank">Getting Started: Web Interface</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-BeagleBone" target="_blank">Hardware: BeagleBone</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Case" target="_blank">Hardware: Case</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Electronics" target="_blank">Hardware: Electronics</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Mounting" target="_blank">Hardware: Mounting</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Pneumatics" target="_blank">Hardware: Pneumatics</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Sensors" target="_blank">Hardware: Sensors</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki" target="_blank">Home</a>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Browser-Interface" target="_blank">Software: Browser Interface</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Data-Handling" target="_blank">Software: Data Handling</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Image-Processing" target="_blank">Software: Image Processing</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Overview" target="_blank">Software: Overview</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Pin-Access" target="_blank">Software: Pin Access</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Sensors-&amp;-Actuators" target="_blank">Software: Sensors &amp; Actuators</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Server-API" target="_blank">Software: Server API</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Server-Code" target="_blank">Software: Server Code</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Terminology" target="_blank">Software: Terminology</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Overview" target="_blank">System Overview</a></li>
-              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Safety" target="_blank">System Safety</a></li>
-            </ul>
-          </div>
-        </div>
-        <!-- End main content -->
-      </div>
-    </div>
-  </body>
-</html>
diff --git a/testing/MCTXWeb/public_html/help.html b/testing/MCTXWeb/public_html/help.html
new file mode 100644 (file)
index 0000000..5760c4e
--- /dev/null
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+    <title>MCTX3420 Web Interface</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
+    <script type="text/javascript" src="static/mctx.gui.js"></script>
+
+    <link rel="stylesheet" type="text/css" href="static/style.css">
+    <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
+    <script type="text/javascript">
+      runBeforeLoad().done(function() {
+        $(document).ready(function() {
+          
+        });
+      })
+
+    </script>
+  </head>
+
+  <body>
+    <div id="header-wrap">
+      <div id="header">
+        <div id="leftnav">
+          <a href="http://www.uwa.edu.au/" target="_blank">
+            <img alt = "The University of Western Australia"
+                 src="static/uwacrest-text.png">
+          </a>
+          <span id="title">Exploding Cans</span>
+        </div>
+        <div id="rightnav">
+          <span id="welcome-container">
+          </span>
+          <span id="date">
+            <script type="text/javascript">getDate();</script>
+          </span>
+          <div id="logout-container">
+            <form action="#">
+              <div>
+                <input type="button" id="logout" value="Logout">
+              </div>
+            </form>
+          </div>
+        </div>
+        <div class="clear"></div>
+      </div>
+    </div>
+    <!-- End header -->
+
+    <div id="content-wrap">
+      <div id="content">
+        <div class="widget" id="sidebar-show">&gt;</div>
+        <div id="sidebar">
+          <div class="widget">
+            <div id="sidebar-hide">&lt;</div>
+            <div class="title">Navigation menu</div>
+            <div id="sidebar-menu" class="nav-menu">
+              <ul>
+                <li><a href="index.html"><span>Home</span></a></li>
+                <li><a href="control.html"><span>Experiment control</span></a></li>
+                <li><a href="graph.html"><span>Experiment graphs</span></a></li>
+                <li><a href="data.html"><span>Experiment data</span></a></li>
+                <li><a href="pintest.html"><span>Pin debugging</span></a></li>
+                <li class="last"><a href="help.html"><span>Help</span></a></li>
+              </ul>
+            </div>
+          </div>
+          <div class="widget justify">
+            <div class="title">Tutorials</div>
+            <p class="justify">For general information on using the MCTX project web interface (i.e. this webpage!) see the <b>'Tutorials'</b> section.
+              This will guide you through setting up, running and analysing an experiment with basic tutorials and explanation of the possible options.</p>
+          </div>
+          <div class="widget justify">
+            <div class="title">Experiment Hardware</div>
+            <p class="justify">For detailed technical information about the experiment hardware, including system diagrams, datasheets and run-downs of the components,
+              see the <b>'Hardware'</b> section.</p>
+          </div>
+          <div class="widget justify">
+            <div class="title">Experiment Software</div>
+            <p class="justify">For detailed technical information about the experiment software, including explanations of its structure, functions and so on, see the
+              <b>'Software'</b> section. Actual code can be accessed on the project's GitHub: <a href="https://github.com/szmoore/MCTX3420">MCTX3420</a>.</p>
+          </div>
+          <div class="widget justify">
+            <div class="title">Contact</div>
+            <p class="justify">If you would like more information about the project or require additional assistance, please contact:</p>
+            <p><b>Adrian Keating</b></p>
+            <p><b>Adam Wittek</b></p>
+          </div>
+        </div>
+        <!-- End sidebar -->
+
+        <div id="main">
+          <div class="widget">
+            <div class="title">Getting Started</div>
+            <p class="justify">Welcome to the MCTX3420 project documentation! On this page, you will find help on how to use the project software,
+              as well as information about the various hardware components.</p>
+            <p class="justify">For a basic introduction to using the experiment, see <b>'Tutorials'</b>.
+              <a href="https://github.com/szmoore/MCTX3420/wiki/System-Overview" target="_blank">System overview</a> provides a general overview of the experiment's hardware, while
+              <a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Web-Interface" target="_blank">Using the web interface</a> provides a tutorial on using the the project software.
+              For detailed information about the experiment hardware, see the <b>'Hardware'</b> section. For detailed information about the project software,
+              see the <b>'Software'</b> section.</p>
+            <div class="sub-title">Tutorials</div>
+            <ul>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Overview" target="_blank">System overview</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Web-Interface" target="_blank">Using the web interface</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Run-an-Experiment" target="_blank">Running an experiment</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-View-Old-Experiments" target="_blank">Viewing previous experiments</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Data-Processing" target="_blank">Data processing</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Safety" target="_blank">System safety</a></li>
+            </ul>
+            <div class="sub-title">Hardware</div>
+            <ul>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Overview" target="_blank">System overview</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Electronics" target="_blank">Electronics</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-BeagleBone" target="_blank">BeagleBone</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Pneumatics" target="_blank">Pneumatics</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Sensors" target="_blank">Sensors</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Mounting" target="_blank">Mounting</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Case" target="_blank">Case</a></li>
+            </ul>
+            <div class="sub-title">Software</div>
+            <ul>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Overview" target="_blank">Software overview</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Browser-Interface" target="_blank">Browser interface</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Server-API" target="_blank">Server API</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Server-Code" target="_blank">Server code</a>
+                <ul>
+                  <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Data-Handling" target="_blank">Data handling</a></li>
+                  <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Sensors-&amp;-Actuators" target="_blank">Sensors and actuators</a></li>
+                  <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Pin-Access" target="_blank">Pin access</a></li>
+                  <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Image-Processing" target="_blank">Image processing</a></li>
+                </ul>
+              </li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Terminology" target="_blank">Terminology</a></li>
+            </ul>
+          </div>
+
+          <div class="widget">
+            <div class="title">Project Wiki</div>
+            <p>All of the project's help documentation is stored in its <a href="https://github.com/szmoore/MCTX3420/wiki" target="_blank">GitHub wiki.</a> This git repository also
+              stores all of the software code for the project; to access the project code files, visit <a href="https://github.com/szmoore/MCTX3420" target="_blank">this link</a>.</p>
+            <p>The following is a list of pages on the GitHub wiki (all of which can be accessed via the categories above):<p>
+            <ul>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Data-Processing" target="_blank">Getting Started: Data Processing</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Run-an-Experiment" target="_blank">Getting Started: Run an Experiment</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-View-Old-Experiments" target="_blank">Getting Started: View Old Experiments</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Web-Interface" target="_blank">Getting Started: Web Interface</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-BeagleBone" target="_blank">Hardware: BeagleBone</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Case" target="_blank">Hardware: Case</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Electronics" target="_blank">Hardware: Electronics</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Mounting" target="_blank">Hardware: Mounting</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Pneumatics" target="_blank">Hardware: Pneumatics</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Sensors" target="_blank">Hardware: Sensors</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki" target="_blank">Home</a>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Browser-Interface" target="_blank">Software: Browser Interface</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Data-Handling" target="_blank">Software: Data Handling</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Image-Processing" target="_blank">Software: Image Processing</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Overview" target="_blank">Software: Overview</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Pin-Access" target="_blank">Software: Pin Access</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Sensors-&amp;-Actuators" target="_blank">Software: Sensors &amp; Actuators</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Server-API" target="_blank">Software: Server API</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Server-Code" target="_blank">Software: Server Code</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Terminology" target="_blank">Software: Terminology</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Overview" target="_blank">System Overview</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Safety" target="_blank">System Safety</a></li>
+            </ul>
+          </div>
+        </div>
+        <!-- End main content -->
+      </div>
+    </div>
+  </body>
+</html>
index 4ffacec..b91db20 100644 (file)
     <script type="text/javascript">
       runBeforeLoad().done(function () {
         $(document).ready(function () {
-         //Show the content!
-         $("#content").css("display", "block");
-         //Set the welcome bar
-         var name = " " + (mctx.friendlyName ? mctx.friendlyName : "");
-         $("#welcome-container").text("Welcome"+ name + "!");
-         $("#logout-container").css("display", "block");
-         $("#logout").click(function () {
-           $("#logout").logout();
-         });
        });       
-      })
+      }).always(function () {
+        $(document).ready(function () {
+          //$("#sidebar-menu").populateNavMenu();
+        });
+      });
 
     </script>
   </head>
       </noscript>
 
       <div id="content">
+        <div class="widget" id="sidebar-show">&gt;</div>
         <div id="sidebar">
           <div class="widget">
+            <div id="sidebar-hide">&lt;</div>
             <div class="title">Navigation menu</div>
-            <div class="nav-menu">
-            <ul>
-               <li><a href="index.html"><span>Home</span></a></li>
-               <li><a href="control.html"><span>Experiment control</span></a></li>
-               <li><a href="pintest.html"><span>Pin debugging</span></a></li>
-               <li class="last"><a href="#"><span>Help</span></a></li>
-            </ul>
+            <div id="sidebar-menu" class="nav-menu">
+              <ul>
+                <li><a href="index.html"><span>Home</span></a></li>
+                <li><a href="control.html"><span>Experiment control</span></a></li>
+                <li><a href="graph.html"><span>Experiment graphs</span></a></li>
+                <li><a href="data.html"><span>Experiment data</span></a></li>
+                <li><a href="pintest.html"><span>Pin debugging</span></a></li>
+                <li class="last"><a href="help.html"><span>Help</span></a></li>
+              </ul>
             </div>
           </div>
-          <div class="widget">
-            <div class="title">Help</div>
-
-          </div>
         </div>
         <!-- End sidebar -->
 
index fbc9411..9372ee8 100644 (file)
         });
         
         $(document).ready(function () {
-         //Set the welcome bar
-         var name = " " + (mctx.friendlyName ? mctx.friendlyName : "");
-         $("#welcome-container").text("Welcome"+ name + "!");
-         $("#content").css("display", "block");
-
-         $("#logout").click(function () {
-           $("#logout").logout();
-         });
-
          $("#gpio-menu").populateDropdown(mctx.pintest.gpios, "GPIO ");
          $("#pwm-menu").populateDropdown(mctx.pintest.pwms, "PWM ");
 
@@ -59,8 +50,6 @@
            }
          });
          $("#adc-controls").trigger("reset").setADCControl();
-
-         $("#errorlog").setErrorLog();
        });
       })
 
       </noscript>
       
       <div id="content">
+        <div class="widget" id="sidebar-show">&gt;</div>
         <div id="sidebar">
           <div class="widget">
+            <div id="sidebar-hide">&lt;</div>
             <div class="title">Navigation menu</div>
-            <div class="nav-menu">
-            <ul>
-               <li><a href="index.html"><span>Home</span></a></li>
-               <li><a href="control.html"><span>Experiment control</span></a></li>
-               <li><a href="pintest.html"><span>Pin debugging</span></a></li>
-               <li class="last"><a href="#"><span>Help</span></a></li>
-            </ul>
+            <div id="sidebar-menu" class="nav-menu">
+              <ul>
+                <li><a href="index.html"><span>Home</span></a></li>
+                <li><a href="control.html"><span>Experiment control</span></a></li>
+                <li><a href="graph.html"><span>Experiment graphs</span></a></li>
+                <li><a href="data.html"><span>Experiment data</span></a></li>
+                <li><a href="pintest.html"><span>Pin debugging</span></a></li>
+                <li class="last"><a href="help.html"><span>Help</span></a></li>
+              </ul>
             </div>
           </div>
           <div class="widget">
diff --git a/testing/MCTXWeb/public_html/static/OpenSansBold.ttf b/testing/MCTXWeb/public_html/static/OpenSansBold.ttf
new file mode 100644 (file)
index 0000000..277a471
Binary files /dev/null and b/testing/MCTXWeb/public_html/static/OpenSansBold.ttf differ
diff --git a/testing/MCTXWeb/public_html/static/base64.js b/testing/MCTXWeb/public_html/static/base64.js
deleted file mode 100644 (file)
index 72bced4..0000000
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (c) 2010 Nick Galbreath
- * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following
- * conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- */
-
-/* base64 encode/decode compatible with window.btoa/atob
- *
- * window.atob/btoa is a Firefox extension to convert binary data (the "b")
- * to base64 (ascii, the "a").
- *
- * It is also found in Safari and Chrome.  It is not available in IE.
- *
- * if (!window.btoa) window.btoa = base64.encode
- * if (!window.atob) window.atob = base64.decode
- *
- * The original spec's for atob/btoa are a bit lacking
- * https://developer.mozilla.org/en/DOM/window.atob
- * https://developer.mozilla.org/en/DOM/window.btoa
- *
- * window.btoa and base64.encode takes a string where charCodeAt is [0,255]
- * If any character is not [0,255], then an DOMException(5) is thrown.
- *
- * window.atob and base64.decode take a base64-encoded string
- * If the input length is not a multiple of 4, or contains invalid characters
- *   then an DOMException(5) is thrown.
- */
-var base64 = {};
-base64.PADCHAR = '=';
-base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
-
-base64.makeDOMException = function() {
-    // sadly in FF,Safari,Chrome you can't make a DOMException
-    var e, tmp;
-
-    try {
-        return new DOMException(DOMException.INVALID_CHARACTER_ERR);
-    } catch (tmp) {
-        // not available, just passback a duck-typed equiv
-        // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error
-        // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype
-        var ex = new Error("DOM Exception 5");
-
-        // ex.number and ex.description is IE-specific.
-        ex.code = ex.number = 5;
-        ex.name = ex.description = "INVALID_CHARACTER_ERR";
-
-        // Safari/Chrome output format
-        ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; };
-        return ex;
-    }
-}
-
-base64.getbyte64 = function(s,i) {
-    // This is oddly fast, except on Chrome/V8.
-    //  Minimal or no improvement in performance by using a
-    //   object with properties mapping chars to value (eg. 'A': 0)
-    var idx = base64.ALPHA.indexOf(s.charAt(i));
-    if (idx === -1) {
-        throw base64.makeDOMException();
-    }
-    return idx;
-}
-
-base64.decode = function(s) {
-    // convert to string
-    s = '' + s;
-    var getbyte64 = base64.getbyte64;
-    var pads, i, b10;
-    var imax = s.length
-    if (imax === 0) {
-        return s;
-    }
-
-    if (imax % 4 !== 0) {
-        throw base64.makeDOMException();
-    }
-
-    pads = 0
-    if (s.charAt(imax - 1) === base64.PADCHAR) {
-        pads = 1;
-        if (s.charAt(imax - 2) === base64.PADCHAR) {
-            pads = 2;
-        }
-        // either way, we want to ignore this last block
-        imax -= 4;
-    }
-
-    var x = [];
-    for (i = 0; i < imax; i += 4) {
-        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
-            (getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
-        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
-    }
-
-    switch (pads) {
-    case 1:
-        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6);
-        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
-        break;
-    case 2:
-        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
-        x.push(String.fromCharCode(b10 >> 16));
-        break;
-    }
-    return x.join('');
-}
-
-base64.getbyte = function(s,i) {
-    var x = s.charCodeAt(i);
-    if (x > 255) {
-        throw base64.makeDOMException();
-    }
-    return x;
-}
-
-base64.encode = function(s) {
-    if (arguments.length !== 1) {
-        throw new SyntaxError("Not enough arguments");
-    }
-    var padchar = base64.PADCHAR;
-    var alpha   = base64.ALPHA;
-    var getbyte = base64.getbyte;
-
-    var i, b10;
-    var x = [];
-
-    // convert to string
-    s = '' + s;
-
-    var imax = s.length - s.length % 3;
-
-    if (s.length === 0) {
-        return s;
-    }
-    for (i = 0; i < imax; i += 3) {
-        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
-        x.push(alpha.charAt(b10 >> 18));
-        x.push(alpha.charAt((b10 >> 12) & 0x3F));
-        x.push(alpha.charAt((b10 >> 6) & 0x3f));
-        x.push(alpha.charAt(b10 & 0x3f));
-    }
-    switch (s.length - imax) {
-    case 1:
-        b10 = getbyte(s,i) << 16;
-        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
-               padchar + padchar);
-        break;
-    case 2:
-        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
-        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
-               alpha.charAt((b10 >> 6) & 0x3f) + padchar);
-        break;
-    }
-    return x.join('');
-}
-
-
diff --git a/testing/MCTXWeb/public_html/static/mctx.control.js b/testing/MCTXWeb/public_html/static/mctx.control.js
new file mode 100644 (file)
index 0000000..24b0448
--- /dev/null
@@ -0,0 +1,166 @@
+/**
+ * Code for the controls page.
+ * @date 19-10-2013
+ */
+
+mctx.control = {};
+mctx.control.api = mctx.api + 'control'
+mctx.control.states = {
+  start : 0,
+  pause : 1,
+  resume : 2,
+  stop : 3,
+  emergency : 4
+};
+mctx.control.state = null;
+
+function toggleControls(running) {
+  if (running) {
+    $("#experiment-stop").show();
+    $("#pressure-widget").show();
+    $("#start-widget").hide();
+  } else {
+    $("#start-widget").show();
+    $("#experiment-stop").hide();
+    $("#pressure-widget").hide();
+  }
+}
+
+$.fn.setStatusUpdater = function () {
+  var result = this;
+  
+  var updater = function () {
+    $.ajax({
+      url : mctx.control.api,
+      data : {'action' : 'identify'}
+    }).done(function (data) {
+      if (!result.checkStatus(data)) {
+        $(result).parent().addClass("fail");
+        setTimeout(updater, 4000);
+        return;
+      }
+
+      var text;
+      var running = false;
+      var fail = false;
+      switch (data.control_state_id) {
+        case mctx.control.states.start:
+          text = "Experiment started - '" + data.control_experiment_name +
+                 "' by " + data.control_user_name;
+          running = true;
+        break;
+        case mctx.control.states.pause:
+          text = "Experiment paused - '" + data.control_experiment_name +
+                 "' by " + data.control_user_name;
+          running = true;
+        break;
+        case mctx.control.states.stop:
+          text = "No experiment running.";
+        break;
+        case mctx.control.states.emergency:
+          text = "Emergency mode - '" + data.control_experiment_name +
+                 "' by " + data.control_user_name;
+          running = true;
+          fail = true;
+        default:
+          text = "Unknown mode: " + data.control_state_id;
+          fail = true;
+      }
+      
+      if (data.control_state_id !== mctx.control.state) {      
+        toggleControls(running);
+        $(result).text(text);
+        if (fail) {
+          $(result).parent().addClass("fail");
+        } else {
+          $(result).parent().addClass("pass");
+        }
+        
+        mctx.control.state = data.control_state_id;
+      }
+      
+      setTimeout(updater, 2000);
+    })
+   .fail(function () {
+     $(result).text("Connection failed.").parent().addClass("fail");
+     setTimeout(updater, 4000);
+   });
+  };
+  
+  updater();
+};
+
+
+$.fn.startExperiment = function (group, experiment, force, result) {
+ $(group).attr('disabled', 'disabled');
+ if (!experiment || !experiment.match(/^[a-zA-Z0-9_-]+$/)) {
+   result.text("Experiment names must be composed of alphanumeric characters" + 
+               " or the characters -_-").addClass("fail");
+   $(group).removeAttr('disabled');
+   return;
+ } 
+ var data = {action : "start", name : experiment};
+ if (force) {
+   data.force = 1;
+ }
+ $.ajax({
+   url : mctx.control.api,
+   data : data
+ }).done(function (data) {
+   if (!result.checkStatus(data)) {
+     return;
+   }
+   result.html("&nbsp;");
+   toggleControls(true);
+ }).always(function () {
+   $(group).removeAttr('disabled');
+ });
+};
+
+$.fn.stopExperiment = function (result) {
+  var stop = this;
+  stop.attr('disabled', 'disabled');
+  result.text("Stopping the experiment...");
+  
+  $.ajax({
+    url : mctx.control.api,
+    data : {action : "stop"}
+  }).done(function (data) {
+    if (!result.checkStatus(data)) {
+      return;
+    }
+    result.html("&nbsp;");
+    toggleControls(false);
+  }).always(function () {
+    stop.removeAttr('disabled');
+  });
+};
+
+$.fn.setPressure = function(pressure, result) {
+  result.html("&nbsp;");
+  
+  for (var k in pressure) {
+    var n = Number(pressure[k]);
+    if (isNaN(n) || n < 0) {
+      result.text("You must give positive numeric values.").addClass("fail");
+      return;
+    }
+    pressure[k] = n;
+  }
+  
+  var set = pressure['set'] + "," + pressure['wait'] + ","
+            pressure['size'] + "," + pressure['count'];
+  $.ajax({
+    url : mctx.api + "actuators",
+    data : {id : mctx.actuator.pressure_regulator, set : set}
+  }).done(function (data) {
+    if (!result.checkStatus(data)) {
+      return;
+    }
+    
+    result.text("Set ok!").removeClass("fail").addClass("pass");
+  });
+};
\ No newline at end of file
index f71b52a..22d14d7 100644 (file)
@@ -14,6 +14,8 @@ mctx.actuators = {};
 mctx.graph.dependent = null;
 mctx.graph.independent = null;
 mctx.graph.timer = null;
+mctx.graph.running = false;
+mctx.graph.chart = null;
 
 /**
  * Helper - Calculate pairs of (dependent, independent) values
@@ -21,13 +23,21 @@ mctx.graph.timer = null;
  * Appends each value pair to the result
  * @returns result
  */
+/**
+ * Helper - Calculate pairs of (dependent, independent) values
+ * Given input as (time, value) pairs for dependent and independent
+ * Appends each value pair to the result
+ * @param {array[][]} dependent Dependent data to be correlated with independent
+ * @param {array[][]} independent Independent data
+ * @param {array[][]} result Storage location
+ * @returns {dataMerge.result}
+ */
 function dataMerge(dependent, independent, result) {
-       
        var j = 0;
        for (var i = 0; i < dependent.length-1; ++i) {
                var start = dependent[i][0];
                var end = dependent[i+1][0];
-               var average = 0; var n = 0;
+               var average = 0, n = 0;
                for (; j < independent.length; ++j) {
                        if (independent[j][0] < start)
                                continue;
@@ -39,31 +49,36 @@ function dataMerge(dependent, independent, result) {
                if (n > 0) {
                        average /= n;
                        result.push([dependent[i][1], average]);
-               }       
+               }
        }
        return result;
 }
 
 /**
  * Helper function adds the sensors and actuators to a form
+ * @param input_type is it a radio? or is it a checkbox?
+ * @param check_first determines whether the first item is checked or not
+ * @param group which group this input belongs to (name field)
  */
-$.fn.deployDevices = function(input_type, check_first) {
-  var formhtml = $(this).html();
-  var formname = $(this).attr("id");
- // formhtml += "<i> Sensors </i>"
-  var checked = "checked";
-  if (!check_first)
-    checked = "";
-  $.each(mctx.sensors, function(key, val) {
-    formhtml += "<input type=\""+input_type+"\" value=\""+val+"\" id=\"sensors\" name=\""+formname+"\""+checked+">" + val + "</input>";
-    checked = "";
-  });
- // formhtml += "<i> Actuators </i>"
-  $.each(mctx.actuators, function(key, val) {
-    formhtml += "<input type=\""+input_type+"\" value=\""+val+"\" id=\"actuators\" name=\""+formname+"\""+checked+">" + val + "</input>";
-    checked = "";
-  });
-  $(this).html(formhtml);
+$.fn.deployDevices = function(input_type, check_first, group) {
+  var container = this;
+  var apply = function(dict, prefix) {
+    $.each(dict, function(key, val) {
+      var attributes = {
+          'type' : input_type, 'value' : key, 'alt' : val,
+          'class' : prefix, 'name' : group, 
+          'id' : prefix + '_' + val //Unique id (name mangling)
+      };
+      var entry = $("<input/>", attributes);
+      var label = $("<label/>", {'for' : prefix + '_' + val, 'text' : val}); 
+      entry.prop("checked", check_first);
+      check_first = false;
+      container.append(entry).append(label);
+    });
+  }
+  
+  apply(mctx.sensors, 'sensors');
+  apply(mctx.actuators, 'actuators');
 };
 
 /**
@@ -71,99 +86,81 @@ $.fn.deployDevices = function(input_type, check_first) {
  * @returns itself (Is this right?)
  */
 $.fn.setDevices = function() {
-       // Query for sensors and actuators
-  var sensor_curtime = 0;
-  var actuator_curtime = 0;
-  return $.when(
-       $.ajax({url : mctx.api + "?sensors"}).done(function(data) {
-               mctx.sensors = $.extend(mctx.sensors, data.sensors);
-      sensor_curtime = data.running_time;
-    }),
-    $.ajax({url : mctx.api + "?actuators"}).done(function(data) {
-      mctx.actuators = $.extend(mctx.actuators, data.actuators);
-      actuator_curtime = data.running_time;
-    })
-  ).then(function() {
-    $("#xaxis").deployDevices("radio", false);
-    $("#yaxis").deployDevices("checkbox", true);
-    var c = Math.max(actuator_curtime, sensor_curtime);
-    $("input[name=current_time]", "#time_range").val(c);
-    
+  // Query for sensors and actuators
+  return $.ajax({
+    url : mctx.api + 'identify', 
+    data : {'sensors' : 1, 'actuators' : 1}
+  }).done(function (data) {
+    mctx.sensors = $.extend(mctx.sensors, data.sensors);
+    mctx.actuators = $.extend(mctx.actuators, data.actuators);
     
+    //Always set the 'time' option to be checked
+    $("#xaxis input").prop('checked', true);  
+    $("#xaxis").deployDevices("radio", false, 'xaxis');
+    $("#yaxis").deployDevices("checkbox", true, 'yaxis');
+    $("#current_time").val(data.running_time);
+    //Add event listeners for when the
+    $(".change input").change(function () {
+      $("#graph").setGraph();
+    });
   });
 };
 
-
-
-
-/**
- * Sets the graphs to graph stuff.
- * @returns {$.fn}
- */
-$.fn.setGraph = function () {
-  clearTimeout(mctx.graph.timer);
-  var sensor_url = mctx.api + "sensors";
-  var actuator_url = mctx.api + "actuators";
-
-  var updateData = function(json, data) {
-    for (var i = 0; i < json.data.length; ++i)
-      data.push(json.data[i]);
-    return data;
-  };
-  var graphdiv = this;
-
-
-  // Determine which actuator/sensors to plot
-  var xaxis = $("input[name=xaxis]:checked", "#xaxis");
-  var yaxis = $("input[name=yaxis]:checked", "#yaxis");
-  var start_time = $("#start_time").val();
-  var end_time = $("#end_time").val();
-  if (!$.isNumeric(start_time)) {
-    start_time = null;
+function setGraphStatus(on, failText, keep) {
+  if (on) {
+    mctx.graph.running = true;
+    $("#status-text").html("&nbsp;");
+    $("#graph-run").prop("value", "Pause");
+  } else {
+    mctx.graph.running = false;
+    if (failText) {
+      $("#status-text").text(failText).addClass("fail");
+    } else if (!keep) {
+      $("#status-text").text("Graph stopped").removeClass("fail");
+    }
+    $("#graph-run").prop("value", "Run");
   }
-  if (!$.isNumeric(end_time)) {
-    end_time = null;
-  }
-
-  var devices = {};
-  xaxis.each(function() {
-    devices[$(this).val()] = {};
-    devices[$(this).val()]["url"] = mctx.api + $(this).attr("id");
-    devices[$(this).val()]["data"] = [];
-    devices[$(this).val()]["start_time"] = start_time;
-    devices[$(this).val()]["end_time"] = end_time;
-  });
-  yaxis.each(function() {
-    devices[$(this).val()] = {};
-    devices[$(this).val()]["url"] = mctx.api + $(this).attr("id");
-    devices[$(this).val()]["data"] = [];
-    devices[$(this).val()]["start_time"] = start_time;
-    devices[$(this).val()]["end_time"] = end_time;
-  });
+}
 
+function graphUpdater() {
+  var urls = {
+    'sensors' : mctx.graph.api.sensors,
+    'actuators' : mctx.graph.api.actuators
+  }
+  
   var updater = function () {
-    var time_limit = 20;
     var responses = [];
     var ctime =  $("#current_time");
     
+    var xaxis = mctx.graph.xaxis;
+    var yaxis = mctx.graph.yaxis;
+    var start_time = mctx.graph.start_time;
+    var end_time = mctx.graph.end_time;
+    var devices = mctx.graph.devices;
+    
+    if (xaxis.size() < 1 || yaxis.size() < 1) {
+      setGraphStatus(false, "No x or y axis selected.");
+      return;
+    }
     
     $.each(devices, function(key, val) {
-      if (devices[key].url === sensor_url || devices[key].url === actuator_url) {
-       // alert("AJAX");
-        //alert(key);
-        //alert(devices[key].url);
-        parameters = {name : key};
-        if (start_time != null) {
-          //alert("start_time = " + start_time);
-          parameters = $.extend(parameters, {start_time : start_time});
+      if (val.urltype in urls) {
+        var parameters = {id : val.id};
+        if (start_time !== null) {
+          parameters.start_time = start_time;
         }
-        if (end_time != null)
-          parameters = $.extend(parameters, {end_time : end_time});
-        responses.push($.ajax({url : devices[key].url, data : parameters}).done(function(json) {
+        if (end_time !== null) {
+          parameters.end_time = end_time;
+        }
+        responses.push($.ajax({url : urls[val.urltype], data : parameters})
+        .done(function(json) {
           //alert("Hi from " + json.name);
-          var dev = devices[json.name].data;
+          if (!$("#status-text").checkStatus(json)) {
+            setGraphStatus(false, null, true); //Don't reset text, checkstatus just set it.
+            return;
+          }
+          
+          var dev = val.data;
           for (var i = 0; i < json.data.length; ++i) {
             if (dev.length <= 0 || json.data[i][0] > dev[dev.length-1][0]) {
               dev.push(json.data[i]);
@@ -177,47 +174,99 @@ $.fn.setGraph = function () {
 
     //... When the response is received, then() will happen (I think?)
     $.when.apply(this, responses).then(function () {
-      
-      var plot_data = [];
-      yaxis.each(function() {
-        //alert("Add " + $(this).val() + " to plot");
-        if (xaxis.val() === "time") {
-          //alert("Against time");
-          plot_data.push(devices[$(this).val()].data);
-        }
-        else {
-          var result = []
-          dataMerge(devices[xaxis.val()].data, devices[$(this).val()].data, result);
-          /*
-          var astr = "[";
-          for (var i = 0; i < result.length; ++i)
-            astr += "[" + result[i][0] + "," + result[i][1] + "]" + ",";
-          astr += "]";
-          alert(astr);
-          */
-          plot_data.push(result);
+      if (mctx.graph.running) {
+        var plot_data = [];
+        
+        yaxis.each(function() {
+          //alert("Add " + $(this).val() + " to plot");
+          if (xaxis.attr("alt") === "time") {
+            //alert("Against time");
+            plot_data.push(devices[$(this).attr("alt")].data);
+          } else {
+            var result = []
+            dataMerge(devices[xaxis.attr("alt")].data, 
+                      devices[$(this).attr("alt")].data, result);
+            plot_data.push(result);
+          }
+        });
+        
+        if (mctx.graph.chart !== null) {
+          mctx.graph.chart.setData(plot_data);
+          mctx.graph.chart.setupGrid(); 
+          mctx.graph.chart.draw();
+        } else {
+          mctx.graph.chart = $.plot("#graph", plot_data);
         }
-      });
-      
-      //alert(plot_data + "");
-      //alert("Plot happened");
-      $.plot("#graph", plot_data);
-      mctx.graph.timer = setTimeout(updater, 1000);
-    }, function () {alert("Graph crashed");});
+        mctx.graph.timer = setTimeout(updater, 1000);
+      }
+    }, function () {
+      setGraphStatus("Connection issue - graph stopped.");
+      //This will always happen when a user closes the page
+      //alert("Graph crashed"); 
+    });
   };
   
+  setGraphStatus(true);
   updater();
+  return this;
+}
+
+/**
+ * Sets the graphs to graph stuff.
+ * @returns {$.fn}
+ */
+$.fn.setGraph = function () {
+  // Determine which actuator/sensors to plot
+  var xaxis = $("#xaxis input[name=xaxis]:checked");
+  var yaxis = $("#yaxis input[name=yaxis]:checked");
+  if (xaxis.size() < 1 || yaxis.size() < 1) {
+    //nothing to plot...
+    setGraphStatus(false, "No x or y axis selected.");
+    return;
+  }
+  
+  var start_time = $("#start_time").val();
+  var end_time = $("#end_time").val();
+  if (!$.isNumeric(start_time)) {
+    start_time = null;
+  }
+  if (!$.isNumeric(end_time)) {
+    end_time = null;
+  }
+
+  var devices = {};
+  var populateDict = function () {
+    var dict = {};
+    dict['urltype'] = $(this).attr("class");
+    dict['id'] = $(this).attr("value");
+    dict['data'] = [];
+    dict['start_time'] = start_time;
+    dict['end_time'] = end_time;
+    devices[$(this).attr("alt")] = dict;
+  };
+  xaxis.each(populateDict);
+  yaxis.each(populateDict);
+  
+  mctx.graph.xaxis = xaxis;
+  mctx.graph.yaxis = yaxis;
+  mctx.graph.start_time = start_time;
+  mctx.graph.end_time = end_time;
+  mctx.graph.devices = devices;
+  
+  if (!mctx.graph.running) {
+    $("#graph-run").val("Pause");
+    $("#status-text").text("")
+    graphUpdater();
+  }
+  
   return this;
 };
 
 $.fn.runButton = function() {
-  //alert($(this).val());
-  if ($(this).val() === "Run") {
-    $("#graph").setGraph();
-    $(this).val("Pause");
-  }
-  else {
+  if (mctx.graph.running) {
+    setGraphStatus(false);
     clearTimeout(mctx.graph.timer);
-    $(this).val("Run");
+  } else {
+    $("#graph").setGraph();
   }
 };
index 773fa23..3df2bac 100644 (file)
@@ -1,12 +1,12 @@
 /**
- * MCTX3420 2013 GUI stuff.
- * Coding style:
- *  - Always end statements with semicolons
- *  - Egyptian brackets are highly recommended (*cough*).
- *  - Don't use synchronous stuff - hook events into callbacks
- *  - $.fn functions should return either themselves or some useful object
- *    to allow for chaining of method calls
- */
+* MCTX3420 2013 GUI stuff.
+* Coding style:
+*  - Always end statements with semicolons
+*  - Egyptian brackets are highly recommended (*cough*).
+*  - Don't use synchronous stuff - hook events into callbacks
+*  - $.fn functions should return either themselves or some useful object
+*    to allow for chaining of method calls
+*/
 
 mctx = {};
 //Don't use this in the final version
@@ -18,291 +18,329 @@ mctx.expected_api_version = 0;
 mctx.has_control = false;
 mctx.debug = true;
 
-mctx.statusCodes = {
-  STATUS_OK : 1
+mctx.menu = [
+    {'text' : 'Home', href : mctx.location + 'index.html'},
+    {'text' : 'Experiment control', href : mctx.location + 'control.html'},
+    {'text' : 'Pin debugging', href : mctx.location + 'pintest.html'},
+    {'text' : 'Help', href : mctx.location + 'help.html'}
+];
+
+mctx.status = {
+    OK : 1,
+    ERROR : -1,
+    UNAUTHORIZED : -2,
+    NOTRUNNING : -3,
+    ALREADYEXISTS : -4
 };
 
 mctx.statusCodesDescription = {
-  "1" : "Ok",
-  "-1" : "General error",
-  "-2" : "Unauthorized",
-  "-3" : "Not running",
-  "-4" : "Already exists"
+    "1" : "Ok",
+    "-1" : "General error",
+    "-2" : "Unauthorized",
+    "-3" : "Not running",
+    "-4" : "Already exists"
 };
 
 mctx.sensors = {
-  0 : {name : "Strain gauge 1"},
-  1 : {name : "Strain gauge 2"},
-  2 : {name : "Strain gauge 3"},
-  3 : {name : "Strain gauge 4"},
-  4 : {name : "Pressure sensor 1"},
-  5 : {name : "Pressure sensor 2"},
-  6 : {name : "Pressure sensor 3"}
+    0 : {name : "Strain gauge 1"},
+    1 : {name : "Strain gauge 2"},
+    2 : {name : "Strain gauge 3"},
+    3 : {name : "Strain gauge 4"},
+    4 : {name : "Pressure sensor 1"},
+    5 : {name : "Pressure sensor 2"},
+    6 : {name : "Pressure sensor 3"}
 };
 
 mctx.actuators = {
-  0 : {name : "Solenoid 1"},
-  1 : {name : "Solenoid 2"},
-  2 : {name : "Solenoid 3"},
-  3 : {name : "Pressure regulator"}
+    0 : {name : "Solenoid 1"},
+    1 : {name : "Solenoid 2"},
+    2 : {name : "Solenoid 3"},
+    3 : {name : "Pressure regulator"}
 };
 
+mctx.actuator = {};
+mctx.actuator.pressure_regulator = 0;
+
 mctx.strain_gauges = {};
 mctx.strain_gauges.ids = [0, 1, 2, 3];
 mctx.strain_gauges.time_limit = 20;
 
 /**
- * Logs a message if mctx.debug is enabled. This function takes
- * a variable number of arguments and passes them 
- * to alert or console.log (based on browser support).
- * @returns {undefined}
- */
+* Logs a message if mctx.debug is enabled. This function takes
+* a variable number of arguments and passes them 
+* to alert or console.log (based on browser support).
+* @returns {undefined}
+*/
 function debugLog () {
-  if (mctx.debug) {
-    if (typeof console === "undefined" || typeof console.log === "undefined") {
-      for (var i = 0; i < arguments.length; i++) {
-        alert(arguments[i]);
-      }
-    } else {
-      console.log.apply(this, arguments);
+    if (mctx.debug) {
+        if (typeof console === "undefined" || typeof console.log === "undefined") {
+            for (var i = 0; i < arguments.length; i++) {
+                alert(arguments[i]);
+            }
+        } else {
+            try {
+              console.log.apply(this, arguments);
+            } catch (e) {
+              //Chromie
+              for (var i = 0; i < arguments.length; i++) {
+                console.log(arguments[i]);
+              }
+            }
+        }
     }
-  }
 }
 
 /**
- * Writes the current date to wherever it's called.
- */
+* Writes the current date to wherever it's called.
+*/
 function getDate() {
-       document.write((new Date()).toDateString());
+    document.write((new Date()).toDateString());
 }
 
 /**
- * Should be run before the load of any GUI page.
- * To hook events to be called after this function runs,
- * use the 'always' method, e.g runBeforeLoad().always(function() {my stuff});
- * @param {type} isLoginPage
- * @returns The return value of calling $.ajax
- */
+* Should be run before the load of any GUI page.
+* To hook events to be called after this function runs,
+* use the 'always' method, e.g runBeforeLoad().always(function() {my stuff});
+* @param {type} isLoginPage
+* @returns The return value of calling $.ajax
+*/
 function runBeforeLoad(isLoginPage) {
-  return $.ajax({
-    url : mctx.api + "identify"
-  }).done(function (data) {
-    if (data.logged_in && isLoginPage) {
-      if (mctx.debug) {
-        debugLog("Redirect disabled!");
-      } else {
-        window.location = mctx.location;
-      }
-    } else if (!data.logged_in && !isLoginPage) {
-      if (mctx.debug) {
-        debugLog("Redirect disabled!");
-      } else {
-        //Note: this only clears the nameless cookie
-        document.cookie = ""; 
-        window.location = mctx.location + "login.html";
-      }
-    } else {
-      mctx.friendlyName = data.friendly_name;
-    }
-  }).fail(function (jqHXR) {
-    if (mctx.debug) {
-      debugLog("Failed to ident server. Is API running?")
-    } else if (!isLoginPage) {
-      window.location = mctx.location + "login.html";
-    }
-  });
+    return $.ajax({
+        url : mctx.api + "identify"
+    }).done(function (data) {
+        if (data.logged_in && isLoginPage) {
+            if (mctx.debug) {
+                debugLog("Redirect disabled!");
+            } else {
+                window.location = mctx.location;
+            }
+        } else if (!data.logged_in && !isLoginPage) {
+            if (mctx.debug) {
+                debugLog("Redirect disabled!");
+            } else {
+                //Note: this only clears the nameless cookie
+                document.cookie = ""; 
+                window.location = mctx.location + "login.html";
+            }
+        } else {
+            mctx.friendlyName = data.friendly_name;
+        }
+    }).fail(function (jqHXR) {
+        if (mctx.debug) {
+            debugLog("Failed to ident server. Is API running?")
+        } else if (!isLoginPage) {
+            window.location = mctx.location + "login.html";
+        }
+    }).always(function () {
+        
+    });
 }
 
 /**
- * Populates a submenu of the navigation bar
- * @param {string} header The header
- * @param {object} items An object representing the submenu items
- * @param {function} translator A function that translates an object item
- *                              into a text and href.
- * @returns {$.fn} Itself
+ * Populates the navigation menu.
  */
-$.fn.populateSubmenu = function(header, items, translator) {
-  var submenuHeader = $("<li/>").append($("<a/>", {text : header, href : "#"}));
-  var submenu = $("<ul/>", {"class" : "submenu"});
-  
-  for (var item in items) {
-    var info = translator(item, items);
-    submenu.append($("<li/>").append(
-          $("<a/>", {text : info.text, 
-                     href : info.href, target : "_blank"})
-    ));
-  }
-  
-  this.append(submenuHeader.append(submenu));
-  return this;
-};
-
-/** 
- * Populates the navigation bar
- */
-$.fn.populateNavbar = function () {
-  var menu = $("<ul/>", {"class" : "menu"});
-  var sensorTranslator = function(item, items) {
-    var href = mctx.api + "sensors?start_time=0&format=tsv&id=" + item;
-    return {text : items[item].name, href : href};
-  };
-  var actuatorTranslator = function(item, items) {
-    var href = mctx.api + "actuators?start_time=0&format=tsv&id=" + item;
-    return {text : items[item].name, href : href};
-  };
-  
-  menu.populateSubmenu("Sensor data", mctx.sensors, sensorTranslator);
-  menu.populateSubmenu("Actuator data", mctx.actuators, actuatorTranslator);
-  menu.appendTo(this);
-  return this;
+$.fn.populateNavMenu = function() {
+    var root = $("<ul/>")
+    for (var i = 0; i < mctx.menu.length; i++) {
+        var item = mctx.menu[i];
+        var entry = $("<li/>").append(
+            $("<a/>", {text : item.text, href: item.href})
+        );
+        root.append(entry);
+    }
+    $(this).append(root);
+    return this;
 }
 
 /**
- * Sets the camera autoupdater
- * Obsolete?
- * @returns {$.fn}
- */
+* Sets the camera autoupdater
+* Obsolete?
+* @returns {$.fn}
+*/
 $.fn.setCamera = function () {
-  var url = mctx.api + "image";  //http://beaglebone/api/image
-  var update = true;
+    var url = mctx.api + "image";  //http://beaglebone/api/image
+    var update = true;
 
-  //Stop updating if we can't retrieve an image!
-  this.error(function() {
-    update = false;
-  });
-  
-  var parent = this;
-  
-  var updater = function() {
-    if (!update) {
-      alert("Cam fail");
-      parent.attr("src", "");
-      return;
-    }
-    
-    parent.attr("src", url + "#" + (new Date()).getTime());
-    
-    setTimeout(updater, 1000);
-  };
-  
-  updater();
-  return this;
+    //Stop updating if we can't retrieve an image!
+    this.error(function() {
+        update = false;
+    });
+
+    var parent = this;
+
+    var updater = function() {
+        if (!update) {
+            alert("Cam fail");
+            parent.attr("src", "");
+            return;
+        }
+        
+        parent.attr("src", url + "#" + (new Date()).getTime());
+        
+        setTimeout(updater, 10000);
+    };
+
+    updater();
+    return this;
 };
 
 /**
- * Sets the strain graphs to graph stuff. Obsolete?
- * @returns {$.fn}
- */
+* Sets the strain graphs to graph stuff. Obsolete?
+* @returns {$.fn}
+*/
 $.fn.setStrainGraphs = function () {
-  var sensor_url = mctx.api + "sensors";
-  var graphdiv = this;
-  
-  var updater = function () {
-    var time_limit = mctx.strain_gauges.time_limit;
-    var responses = new Array(mctx.strain_gauges.ids.length);
-    
-    for (var i = 0; i < mctx.strain_gauges.ids.length; i++) {
-      var parameters = {id : i, start_time: -time_limit};
-      responses[i] = $.ajax({url : sensor_url, data : parameters});
-    }
-    
-    $.when.apply(this, responses).then(function () {
-      var data = new Array(arguments.length);
-      for (var i = 0; i < arguments.length; i++) {
-        var raw_data = arguments[i][0].data;
-        var pruned_data = [];
-        var step = ~~(raw_data.length/100);
-        for (var j = 0; j < raw_data.length; j += step)
-          pruned_data.push(raw_data[j]); 
-        data[i] = pruned_data;
-      }
-      $.plot(graphdiv, data);
-      setTimeout(updater, 500);
-    }, function () {debugLog("It crashed");});
-  };
-  
-  updater();
-  return this;
+    var sensor_url = mctx.api + "sensors";
+    var graphdiv = this;
+
+    var updater = function () {
+        var time_limit = mctx.strain_gauges.time_limit;
+        var responses = new Array(mctx.strain_gauges.ids.length);
+        
+        for (var i = 0; i < mctx.strain_gauges.ids.length; i++) {
+            var parameters = {id : i, start_time: -time_limit};
+            responses[i] = $.ajax({url : sensor_url, data : parameters});
+        }
+        
+        $.when.apply(this, responses).then(function () {
+            var data = new Array(arguments.length);
+            for (var i = 0; i < arguments.length; i++) {
+                var raw_data = arguments[i][0].data;
+                var pruned_data = [];
+                var step = ~~(raw_data.length/100);
+                for (var j = 0; j < raw_data.length; j += step)
+                pruned_data.push(raw_data[j]); 
+                data[i] = pruned_data;
+            }
+            $.plot(graphdiv, data);
+            setTimeout(updater, 1000);
+        }, function () {debugLog("It crashed");});
+    };
+
+    updater();
+    return this;
 };
 
 /**
- * Performs a login attempt.
- * @returns The AJAX object of the login request */
+* Performs a login attempt.
+* @returns The AJAX object of the login request */
 $.fn.login = function () {
-  var username = this.find("input[name='username']").val();
-  var password = this.find("input[name='pass']").val();
-  var out = this.find("#result");
-  var redirect = function () {
-    window.location.href = mctx.location;
-  };
-  
-  out.removeAttr("class");
-  out.text("Logging in...");
-  
-  return $.ajax({
-    url : mctx.api + "bind",
-    data : {user: username, pass : password}
-  }).done(function (data) {
-    if (data.status < 0) {
-      mctx.has_control = false;
-      out.attr("class", "fail");
-      out.text("Login failed: " + data.description);
-    } else {
-      //todo: error check
-      mctx.has_control = true;
-      out.attr("class", "pass");
-      out.text("Login ok!");
-      setTimeout(redirect, 800);      
-    }
-  }).fail(function (jqXHR) {
-    mctx.has_control = false;
-    out.attr("class", "fail");
-    out.text("Login request failed - connection issues.")
-  });
+    var username = this.find("input[name='username']").val();
+    var password = this.find("input[name='pass']").val();
+    var out = this.find("#result");
+    var redirect = function () {
+        window.location.href = mctx.location;
+    };
+
+    out.removeAttr("class");
+    out.text("Logging in...");
+
+    return $.ajax({
+        url : mctx.api + "bind",
+        data : {user: username, pass : password}
+    }).done(function (data) {
+        if (data.status < 0) {
+            mctx.has_control = false;
+            out.attr("class", "fail");
+            out.text("Login failed: " + data.description);
+        } else {
+            //todo: error check
+            mctx.has_control = true;
+            out.attr("class", "pass");
+            out.text("Login ok!");
+            setTimeout(redirect, 800);      
+        }
+    }).fail(function (jqXHR) {
+        mctx.has_control = false;
+        out.attr("class", "fail");
+        out.text("Login request failed - connection issues.")
+    });
 };
 
 /**
- * Performs a logout request. The nameless cookie is
- * always cleared and the browser redirected to the login page,
- * independent of whether or not logout succeeded.
- * @returns  The AJAX object of the logout request.
- */
+* Performs a logout request. The nameless cookie is
+* always cleared and the browser redirected to the login page,
+* independent of whether or not logout succeeded.
+* @returns  The AJAX object of the logout request.
+*/
 $.fn.logout = function () {
-  return $.ajax({
-    url : mctx.api + "unbind"
-  }).always(function () {
-    //Note: this only clears the nameless cookie
-    document.cookie = ""; 
-    window.location = mctx.location + "login.html";
-  });
+    return $.ajax({
+        url : mctx.api + "unbind"
+    }).always(function () {
+        //Note: this only clears the nameless cookie
+        document.cookie = ""; 
+        window.location = mctx.location + "login.html";
+    });
 };
 
 /**
- * Sets the error log to continuously update.
- * @returns itself */
+* Sets the error log to continuously update.
+* @returns itself */
 $.fn.setErrorLog = function () {
-  var url = mctx.api + "errorlog";
-  var outdiv = this;
+    var url = mctx.api + "errorlog";
+    var outdiv = this;
+
+    if ($(this).length <= 0) {
+      //No error log, so do nothing.
+      return;
+    }
+
+    var updater = function () {
+        $.ajax({url : url}).done(function (data) {
+            outdiv.text(data);
+            outdiv.scrollTop(
+              outdiv[0].scrollHeight - outdiv.height()
+            );
+            setTimeout(updater, 3000);
+        }).fail(function (jqXHR) {
+            if (jqXHR.status === 502 || jqXHR.status === 0) {
+                outdiv.text("Failed to retrieve the error log.");
+            }
+            setTimeout(updater, 10000); //poll at slower rate
+        });
+    };
+
+    updater();
+    return this;
+};
+
+$.fn.checkStatus = function(data) {
+  if (data.status !== mctx.status.OK) {
+    $(this).text(data.description).removeClass("pass").addClass("fail");
+    return false;
+  }
+  return true;
+};
+
+$(document).ready(function () {
+  //Show the content!
+  $("#content").css("display", "block");
   
-  var updater = function () {
-    $.ajax({url : url}).done(function (data) {
-      outdiv.text(data);
-      outdiv.scrollTop(
-        outdiv[0].scrollHeight - outdiv.height()
-      );
-      setTimeout(updater, 2000);
-    }).fail(function (jqXHR) {
-      if (jqXHR.status === 502 || jqXHR.status === 0) {
-        outdiv.text("Failed to retrieve the error log.");
-      }
-      setTimeout(updater, 4000);
-    });
-  };
+  //Set the welcome bar
+  var name = " " + (mctx.friendlyName ? mctx.friendlyName : "");
+  $("#welcome-container").text("Welcome"+ name + "!");
+  $("#logout-container").css("display", "block");
+  //$("#menu-container").populateNavbar();
+
+  $("#logout").click(function () {
+    $("#logout").logout();
+  });
   
-  updater();
-  return this;
-};
+  //Enable the error log, if present
+  $("#errorlog").setErrorLog();
+  
+  //Enable the hide/show clicks
+  $("#sidebar-hide").click(function () {
+    $("#sidebar").css("display", "none");
+    $("#sidebar-show").css("display", "inherit");
+    return this;
+  });
 
+  $("#sidebar-show").click(function () {
+    $("#sidebar-show").css("display", "none");
+    $("#sidebar").css("display", "inherit");
+    return this;
+  });
+});
 $(document).ajaxError(function (event, jqXHR) {
-  //console.log("AJAX query failed with: " + jqXHR.status + " (" + jqXHR.statusText + ")");
+    //console.log("AJAX query failed with: " + jqXHR.status + " (" + jqXHR.statusText + ")");
 });
\ No newline at end of file
index e9a8623..f282aba 100644 (file)
@@ -3,6 +3,13 @@
   font-family: "Open Sans";
   src: url("OpenSans.ttf"),
     url("OpenSans.eot");
+  font-weight: normal;
+}
+
+@font-face {
+  font-family: "Open Sans";
+  src: url("OpenSansBold.ttf");
+  font-weight: bold;
 }
 
 body {
@@ -18,13 +25,6 @@ hr {
   border-bottom: 1px solid gray;
 }
 
-form.controls {
-  background-color: #F9F9F9;
-  border: 1px solid #808080;
-  padding: 1em;
-  margin: 1em auto;
-}
-
 a {
   color: #1188DD;
 }
@@ -47,7 +47,7 @@ img {
   font-weight: bold
 }
 
-div.centre {
+div.centre, p.centre {
   text-align: center;
   margin: auto;
 }
@@ -56,6 +56,23 @@ div.centre {
   font-weight: bold;
 }
 
+.medium {
+  font-size: 115% !important;
+}
+
+.large {
+  font-size: 130% !important;
+}
+
+.normal {
+  font-size: inherit !important;
+}
+
+.small {
+  font-size: 80% !important;
+}
+
+/*
 table {
   border: none;
 }
@@ -69,6 +86,7 @@ table.status, table.status tr, table.status td {
     padding: 0.2em 0.75em;
 }
 
+
 td {
   padding: 0 0.5em;
 }
@@ -78,6 +96,54 @@ th {
   border-bottom: 1px solid gray;
 }
 
+*/
+
+table {
+  border-radius: 3px;
+  border-collapse: collapse;
+  word-wrap: break-word;
+  line-height: 1.3em;
+  width: 100%;
+  margin: 1em auto;
+  vertical-align: middle;
+}
+
+td {
+  padding: 0.2em 0.5em;
+}
+
+tr.pass {
+  color: #000;
+  background-color: #7AE309;
+  font-weight: inherit;
+}
+
+tr.fail, tr.fail .fail {
+  color: #000;
+  background-color: #E30909;
+  font-weight: inherit;
+}
+
+table.centre {
+  margin: auto;
+  text-align: center;
+}
+
+table.horizontal {
+  line-height: 1.6em;
+}
+
+table.horizontal th {
+  text-align: left;
+  border-bottom: 0;
+  width: 15%;
+  font-weight: normal;
+}
+
+table.horizontal.medium th {
+  width: 22%;
+}
+
 img.centre {
   display: block;
   margin: auto;
@@ -110,6 +176,34 @@ input[type="text"]:focus, input[type="password"]:focus {
   box-shadow: 0 0 2px #BBBBBB;
 }
 
+form.nice {
+  line-height: 1.8em;
+}
+
+form.nice p {
+  margin: 0.25em auto;
+}
+
+form.nice input {
+  font-size: 150%;
+}
+
+form.nice label {
+  font-size: 110%;
+}
+
+form.nice input[type="submit"], form.nice input[type="button"] {
+  font-size: inherit;
+  padding: 0.4em 0.8em;
+}
+
+form.controls {
+  background-color: #F9F9F9;
+  border: 1px solid #808080;
+  padding: 1em;
+  margin: 1em auto;
+}
+
 #header-wrap {
   background-color: #2a2a2a;
   border-top: 0.3em solid #1188DD;
@@ -121,7 +215,7 @@ input[type="text"]:focus, input[type="password"]:focus {
 
 #header {
   padding: 1.5em 2em;
-  max-width: 1280px;
+  /*max-width: 1920px;*/
   margin: 0 auto;
 }
 
@@ -185,8 +279,9 @@ input[type="text"]:focus, input[type="password"]:focus {
 
 #content-wrap {
   padding: 1em 2em;
-  max-width: 1280px;
+  /*max-width: 1920px;*/
   margin: 1em auto;
+  transition: all 0.2s ease 0s;
 }
 
 #content  {
@@ -196,13 +291,32 @@ input[type="text"]:focus, input[type="password"]:focus {
 
 #sidebar{
   float: left;
-  max-width: 26%;
+  width: 240px;
+  transition: all 0.2s ease 0s;
 }
 
 #sidebar .title {
   font-size: 20px;
 }
 
+#sidebar-hide {
+  border-bottom: 1px solid #CCCCCC;
+  border-left: 1px solid #CCCCCC;
+  cursor: pointer;
+  float: right;
+  height: 20px;
+  margin-right: -1.25em;
+  margin-top: -1em;
+  text-align: center;
+  width: 20px;
+}
+
+#sidebar-show {
+  float: left;
+  cursor: pointer;
+  display: none;
+}
+
 .justify {
   text-align: justify;
 }
@@ -214,11 +328,31 @@ input[type="text"]:focus, input[type="password"]:focus {
 }
 
 #main .sub-title {
-  font-size: 18px;
+  font-size: 115%;
   font-weight: bold;
   margin-bottom: 0.25em;
 }
 
+/* Unused ?? */
+.sxs {
+  display: table;
+  table-layout: fixed;
+  width: 100%;
+  border-spacing: 0.5em;
+}
+
+.sxs div {
+  display: table-cell;
+}
+
+.left {
+  float: left;
+}
+
+.right {
+  float: right;
+}
+
 .title {
   font-size: 24px;
   font-weight: bold;
@@ -246,22 +380,11 @@ input[type="text"]:focus, input[type="password"]:focus {
 
 .plot {
        box-sizing: border-box;
-       width: 850px;
-       height: 450px;
+       width: auto;
+       height: 300px;
        padding: 20px 15px 15px 15px;
        margin: 15px auto 30px auto;
        border: 1px solid #ddd;
-       background: #fff;
-       background: linear-gradient(#f6f6f6 0, #fff 50px);
-       background: -o-linear-gradient(#f6f6f6 0, #fff 50px);
-       background: -ms-linear-gradient(#f6f6f6 0, #fff 50px);
-       background: -moz-linear-gradient(#f6f6f6 0, #fff 50px);
-       background: -webkit-linear-gradient(#f6f6f6 0, #fff 50px);
-       box-shadow: 0 3px 10px rgba(0,0,0,0.15);
-       -o-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
-       -ms-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
-       -moz-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
-       -webkit-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
        font-size:20px;
 }
 
@@ -275,6 +398,8 @@ input[type="text"]:focus, input[type="password"]:focus {
   max-width: 100%;
   width: 100%;
   height: 6em;
+  background-color: white;
+  border: 1px solid #ccc;
 }
 
 /** For login.html **/
@@ -306,3 +431,16 @@ input[type="text"]:focus, input[type="password"]:focus {
 #login-container #result {
   clear: both;
 }
+
+#experiment-stop {
+  padding: 0.2em 0.4em;
+  display: none;
+}
+
+#start-widget {
+  display: none;
+}
+
+#pressure-widget {
+  display: none;
+}
\ No newline at end of file
diff --git a/testing/pygui/pygui.py b/testing/pygui/pygui.py
new file mode 100755 (executable)
index 0000000..a27e003
--- /dev/null
@@ -0,0 +1,89 @@
+#!/usr/bin/python
+
+"""
+       Python has a different style of documentation which will break doxygen...
+"""
+
+import sys
+import os
+import matplotlib.pyplot as plt
+import numpy
+import requests
+import datetime
+import time
+
+#TODO: Replace with URL of testing server
+api_url = "https://daedalus/api"
+
+def log(message):
+       sys.stderr.write("%s: %s : %s\n" % (sys.argv[0], str(datetime.datetime.now()), message))
+
+def update_plot(plot, axes, data_x, data_y):
+       """
+               Update data to plot, allegedly this is faster than just replotting everything
+       """
+       plot.set_xdata(numpy.append(plot.get_xdata(), data_x))
+       plot.set_ydata(numpy.append(plot.get_ydata(), data_y))
+       axes.relim()
+       axes.autoscale_view()
+       plt.draw()      
+
+def main(argv):
+       if (len(argv) < 2):
+               sys.stderr.write("Usage: %s sensor_id\n" % argv[0])
+               sys.stderr.write("Identifying sensors...\n\n")
+               r = requests.get(api_url + "?sensors", verify=False)            
+               print r.text
+               return 1        
+       
+       plt.ion()
+
+       fig = plt.figure()
+       axes = fig.add_subplot(111)
+       #NOTE: Comma here is *not* a typo and is extremely important and some kind of mysterious python magical tuple thing
+       #               Do not remove the comma or things will break. Horribly.
+       plot, = axes.plot([],[])
+
+       start_time = 0
+       
+       while True:
+               params = { "id" : argv[1], "start_time" : "-1", "format" : "tsv"}
+               try:
+                       r = requests.get(api_url + "/sensors", params=params, verify=False)
+               except:
+                       log("Failed to make request for data");
+                       return 1
+
+               if r.status_code != 200:
+                       log("Bad status code %d" % r.status_code)
+                       print r.text
+                       return 1
+               
+               log("Got data")
+
+               data_x = []
+               data_y = []             
+
+               count = 0
+               for line in r.text.split("\n"):
+                       count += 1
+
+                       
+                       point = map(float, line.split("\t"))
+
+                       if point[0] > start_time:
+                               data_x.append(point[0])
+                               data_y.append(point[1])
+                               start_time = point[0]
+
+               if count > 0:
+                       update_plot(plot, axes, data_x, data_y)
+                       time.sleep(0.5)
+
+       return 0
+
+# ... This is how you make main work in python. With string comparisons.
+if __name__ == "__main__":
+       exit(main(sys.argv))
+
+

UCC git Repository :: git.ucc.asn.au