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
+--- Day changed Mon Oct 14 2013
+10:03 -!- jtanx [
[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+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: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
+--- 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
+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: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)
+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
+23:28 -!- jtanx_ [
[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+--- Day changed Thu Oct 17 2013
+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]"]
+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 -!- 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
+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: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
+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]"]
# 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
// Files containing GPIO and PWM definitions
#include "bbb_pin.h"
-
-
-
/** Number of actuators **/
int g_num_actuators = 0;
* @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)
{
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;
}
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);
}
/**
{
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);
*/
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);
}
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,
* @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)))
{
}
// 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;
}
/**
*/
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 = "";
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;
// 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)))
return;
}
Actuator_SetControl(a, &c);
-
}
// Begin response
SanityFn sanity;
/** Cleanup function **/
CleanFn clean;
+ /** Last setting **/
+ DataPoint last_setting;
} Actuator;
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
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)
#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
/** 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
/** 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);
* @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");
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..
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;
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);
}
}
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.";
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;
* 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;
}
} 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);
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
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;
}
#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))
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) {
}
/**
- * 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; // :-)
}
/**
*/
void FCGI_ReleaseControl(FCGIContext *context) {
*(context->control_key) = 0;
+ // Note: context->user_name should *not* be cleared
return;
}
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));
*/
void FCGI_JSONDouble(const char *key, double value)
{
- printf(",\r\n\t\"%s\" : %f", key, value);
+ printf(",\r\n\t\"%s\" : %.9f", key, value);
}
/**
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
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;
}
{
FCGI_RejectJSON(&context, "Unhandled module");
}
-
-
-
}
Log(LOGDEBUG, "Thread exiting.");
#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?**/
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);
#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);
#include "common.h"
+extern void Image_Init();
extern void Image_Handler(FCGIContext * context, char * params);
+extern void Image_Cleanup();
#endif //_IMAGE_H
/**
* 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];
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] == ':')
{
if (strcmp(user,buffer) == 0)
{
- Log(LOGDEBUG,"User matches! %s\n", buffer);
+ //Log(LOGDEBUG,"User matches! %s\n", buffer);
break;
}
passwd_index = -1;
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;
}
}
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;
}
/**
user[i] = '\0';
- bool authenticated = true;
+ UserType user_type = USER_UNAUTH;
switch (g_options.auth_method)
{
//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)
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)");
}
}
}
#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
*/
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] != '-')
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;
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')
{
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
*/
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, ¶m) < 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();
/** 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;
/** 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 **/
# 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
* @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)
{
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;
}
*/
#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);
{
// 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);
}
-/**
- * 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
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
*/
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;
FCGI_RejectJSON(context, "Negative sampling speed!");
return;
}
- s->sample_us = 1e6*sample_s;
+ DOUBLE_TO_TIMEVAL(sample_s, &(s->sample_time));
}
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;
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
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
--- /dev/null
+/**
+ * @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;
+}
--- /dev/null
+/**
+ * @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
+
+
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);
default:
Fatal("Unknown StrainID %d", id);
return -1; // Should never happen
- }
+ }
}
/**
<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">></div>
<div id="sidebar">
<div class="widget">
+ <div id="sidebar-hide"><</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">
+
+ </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">
+
+ </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>
+++ /dev/null
-<!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>
--- /dev/null
+<!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">></div>
+ <div id="sidebar">
+ <div class="widget">
+ <div id="sidebar-hide"><</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>
<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">></div>
+ <div id="sidebar">
+ <div class="widget">
+ <div id="sidebar-hide"><</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"> </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
+++ /dev/null
-<!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-&-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-&-Actuators" target="_blank">Software: Sensors & 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>
--- /dev/null
+<!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">></div>
+ <div id="sidebar">
+ <div class="widget">
+ <div id="sidebar-hide"><</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-&-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-&-Actuators" target="_blank">Software: Sensors & 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>
<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">></div>
<div id="sidebar">
<div class="widget">
+ <div id="sidebar-hide"><</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 -->
});
$(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 ");
}
});
$("#adc-controls").trigger("reset").setADCControl();
-
- $("#errorlog").setErrorLog();
});
})
</noscript>
<div id="content">
+ <div class="widget" id="sidebar-show">></div>
<div id="sidebar">
<div class="widget">
+ <div id="sidebar-hide"><</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">
+++ /dev/null
-/*
- * 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('');
-}
-
-
--- /dev/null
+/**
+ * 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(" ");
+ 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(" ");
+ toggleControls(false);
+ }).always(function () {
+ stop.removeAttr('disabled');
+ });
+};
+
+$.fn.setPressure = function(pressure, result) {
+ result.html(" ");
+
+ 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
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
* 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;
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');
};
/**
* @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(" ");
+ $("#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]);
//... 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();
}
};
/**
- * 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
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
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 {
border-bottom: 1px solid gray;
}
-form.controls {
- background-color: #F9F9F9;
- border: 1px solid #808080;
- padding: 1em;
- margin: 1em auto;
-}
-
a {
color: #1188DD;
}
font-weight: bold
}
-div.centre {
+div.centre, p.centre {
text-align: center;
margin: auto;
}
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;
}
padding: 0.2em 0.75em;
}
+
td {
padding: 0 0.5em;
}
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;
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;
#header {
padding: 1.5em 2em;
- max-width: 1280px;
+ /*max-width: 1920px;*/
margin: 0 auto;
}
#content-wrap {
padding: 1em 2em;
- max-width: 1280px;
+ /*max-width: 1920px;*/
margin: 1em auto;
+ transition: all 0.2s ease 0s;
}
#content {
#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;
}
}
#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;
.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;
}
max-width: 100%;
width: 100%;
height: 6em;
+ background-color: white;
+ border: 1px solid #ccc;
}
/** For login.html **/
#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
--- /dev/null
+#!/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))
+
+