From: Jeremy Tan Date: Sat, 12 Oct 2013 12:25:38 +0000 (+0800) Subject: Merge branch 'master' of https://github.com/szmoore/MCTX3420.git X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=c931502bda5042411d5757294e3a1ef8df638c44;hp=8dfd2def9f3c84c4d85c073d07912a22e107713f;p=matches%2FMCTX3420.git Merge branch 'master' of https://github.com/szmoore/MCTX3420.git --- diff --git a/irc/log b/irc/log index ad3abf9..034c228 100644 --- a/irc/log +++ b/irc/log @@ -4470,3 +4470,461 @@ 18:01 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has joined #mctxuwa_softdev 19:37 -!- MctxBot [~twang@106-68-47-96.dyn.iinet.net.au] has quit [Ping timeout] 21:16 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"] +--- Day changed Wed Oct 09 2013 +08:29 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has joined #mctxuwa_softdev +09:20 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has quit [Ping timeout] +09:54 -!- MctxBot [~twang@106-68-47-96.dyn.iinet.net.au] has joined #mctxuwa_softdev +10:52 -!- jtanx [~asfa@130.95.133.152] has joined #mctxuwa_softdev +10:55 -!- jtanx_ [~asfa@130.95.54.13] has joined #mctxuwa_softdev +10:56 < jtanx_> welp the usb microscope arrived +10:56 < jtanx_> it uses uvcvideo too +11:07 -!- jtanx [~asfa@130.95.133.152] has quit [Connection reset by peer] +12:00 < jtanx_> shit, we have to coordinate one report across the whole cohort +12:02 < sam_moore> Yeah, shit +12:03 < sam_moore> This unit has not been run very well +12:03 < sam_moore> Except the tutorials +12:06 < sam_moore> If I still cared I +12:06 < sam_moore> d try and take charge of putting the report together +12:07 < sam_moore> But I know that if no one else does it we'll all pass anyway since you can't fail an entire class +12:07 < sam_moore> So terrible +12:07 < sam_moore> We should just start writing a chapter on the software +12:09 < sam_moore> I wonder if we could use the wiki format and export it as a pdf somehow +12:11 < sam_moore> https://github.com/szmoore/MCTX3420/wiki/Hardware:-Pneumatics +12:11 < sam_moore> Hilarious +12:41 < jtanx_> ~.~ +12:41 < jtanx_> our final report will be the wiki! +12:42 < jtanx_> made some progress on the camera +12:42 < jtanx_> lowering the resolution to 352x288 +12:42 < jtanx_> and it will work with openc +12:42 < jtanx_> opencv +12:42 < jtanx_> ffmpeg's fine with 640x480 though +12:42 < jtanx_> opencv just sucks +12:57 < jtanx_> good 'ol stream.html +13:05 < jtanx_> those fatal checks in sensor.c are bad +13:05 < jtanx_> because half the time adc reads will fail and the whole program just crashes +13:07 < jtanx_> oh right I saw your email, nevermind +13:08 < jtanx_> I'm not getting the warnings that you're seeing either +13:12 < sam_moore> Yeah, those warnings were actually on my laptop though, they don't seem to appear on the BeagleBone +13:12 < sam_moore> I'm redoing the sensors code a fair bit +13:12 < sam_moore> You'll probably hate it :P +13:12 < sam_moore> Well, it's not really redoing the structure of how they work +13:13 < sam_moore> Just separating the logic of how sensors get read from all that control loop stuf +13:14 < sam_moore> I'm going to keep with the "one thread per sensor" idea, because that is definitely the simplest +13:16 < sam_moore> Did you tell Omid that they shouldn't assume anything about the state of the GPIO pins before the software starts? +13:16 < sam_moore> Which means that if (unlikely, but I don't know what they're doing) having two pins on at once would cause a catastrophic failure... +13:16 < sam_moore> By murphy's law, it will almost certainly happen +13:33 -!- jtanx_ [~asfa@130.95.54.13] has quit [Ping timeout] +13:59 -!- jtanx [~asfa@130.95.121.247] has joined #mctxuwa_softdev +13:59 -!- jtanx [~asfa@130.95.121.247] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"] +14:51 -!- jtanx [~asfa@130.95.248.194] has joined #mctxuwa_softdev +14:52 < jtanx> oh yah, somehow electronics ordered a non-powered usb hub +14:52 < jtanx> I'm pretty sure we told them to get a powered usb hub... +14:57 < sam_moore> Yep, and I remember them saying that is what they were going to get +14:57 < sam_moore> Dammit, I'm stuck trying to pick the best name for something -_- +14:57 < sam_moore> The worst place to be stuck +15:19 < jtanx> ?? +15:19 < jtanx> thesaurus.com +15:23 < sam_moore> Hahaha +15:23 < sam_moore> I decided the thing didn't really need to exist +15:23 < sam_moore> Problem solved +15:24 < sam_moore> If someone who knows absolutely nothing about programming tries to add sensors to our program they will still be confused +15:24 < jtanx> Hahaha +15:24 < sam_moore> But I hope I've made it easier for someone who knows what they're doing +15:24 < jtanx> Okay +15:24 < jtanx> what were the compiler warnings that you got +15:25 < sam_moore> pin_test.c:41:10: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast] +15:25 < sam_moore> pin_test.c:48:10: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast] +15:25 < sam_moore> More on lines 54, 128, 190,204 +15:25 < jtanx> weird +15:25 < jtanx> I never saw any of those +15:25 < sam_moore> Also I think the code I just wrote for the Strain sensors gives some as well +15:25 < sam_moore> Yes, it does +15:26 < jtanx> did you change GPIO_Export to use int type +15:26 < jtanx> wait +15:26 < jtanx> what am I saying +15:26 < sam_moore> It already uses int type? +15:26 < sam_moore> But it's something like that +15:26 < sam_moore> It looks like you're using unsigned char for something +15:27 < sam_moore> int gpio_num = Strain_To_GPIO(id); +15:27 < sam_moore> GPIO_Export(gpio_num); +15:27 < sam_moore> Is the sort of thing that causes the warning +15:27 < sam_moore> But I have "bool GPIO_Export(int pin)" in bbb_pin.c +15:27 < jtanx> oh right +15:27 < sam_moore> So it makes no sense +15:28 < jtanx> hmm +15:29 < sam_moore> Ooh +15:29 < sam_moore> It must be something to do with the "stub" functions +15:29 < jtanx> Is this on compilation on the BBB +15:29 < sam_moore> No, BBB was fine last time I checked +15:29 < jtanx> okay,w ell I'm not getting any warnings +15:29 < sam_moore> Compiling on my laptop because it's faster +15:30 < sam_moore> #define GPIO_Export(pin) True_Stub((void*)pin) +15:30 < sam_moore> That means the int is getting cast to a void* +15:30 < jtanx> yeah on my computer it's not putting out any warnings +15:30 < jtanx> int to void* is fine +15:30 < jtanx> anything to void* is fine +15:30 < jtanx> actually +15:30 < jtanx> is your system 64 bit +15:30 < sam_moore> 64 bit +15:31 < jtanx> yeah +15:31 < sam_moore> (You still have 32 bit :P) +15:31 < jtanx> well that would explain it +15:31 < sam_moore> Ok, it doesn't matter +15:31 < sam_moore> The stub function doesn't actually do anything with the value +15:31 < jtanx> cast int to int64 or whatever it is +15:32 < sam_moore> That would cause warnings on the BBB though? +15:32 < jtanx> only for the stub functions +15:32 < jtanx> and only for 64 bit +15:32 < jtanx> more ifdefs, yay +15:32 < sam_moore> ... +15:32 < jtanx> or we could get rid of the stubs +15:32 < sam_moore> I'm just going to ignore the warnings +15:32 < sam_moore> That could also work +15:32 < sam_moore> It doesn't really matter +15:32 < jtanx> and just do Wno-unsued-function or someting +15:32 < jtanx> yeah, oh well +15:35 < jtanx> One of the suggestions for the dilatometer was to just take a photo at the start and at the end +15:35 < jtanx> then do comparison +15:35 < sam_moore> Um, ok +15:35 < sam_moore> If they just want 2 data points +15:35 < jtanx> well +15:35 < jtanx> if we can find something other than opencv +15:36 < sam_moore> Ah +15:36 < sam_moore> How slow is it? +15:36 < jtanx> that may help, because right now opencv only plays nice with 352x288 or some other crap resolution +15:36 < jtanx> it's not that it's slow +15:36 < jtanx> is that it only works if the resolution is ~ 350 pixels x 2xx pixels +15:37 < jtanx> ffmpeg is fine with 640x480 afaik +15:37 < jtanx> it could probably do higher but haven't tested that +15:38 < sam_moore> http://libccv.org/ +15:38 < sam_moore> Maybe +15:39 < jtanx> or what if we framegrabbed with some external softare +15:39 < jtanx> then used opencv to process the saved image +15:39 < sam_moore> Yes, I was about to suggest that +15:39 < sam_moore> I'm assuming the problem with OpenCV is that it doesn't like taking images from cameras directly of that resolution +15:39 < sam_moore> And not some underlying problem with having large CvMats or something +15:40 < jtanx> no idea why +15:40 < jtanx> some obscure setting can probably fix it or something +15:40 < jtanx> but I'm not about to trawl though opencv documentaiton now +15:40 < sam_moore> Actually OpenCV works with my 640x480 webcam, so it's probably some low level arm specific problem +15:40 < sam_moore> Which would probably mean that other image processing libraries would also suck +15:40 < jtanx> it may be that too +15:41 < jtanx> I know that with ffmpeg it was spitting warnings at 640x480 +15:41 < jtanx> like non-monotonically increasing timestamp +15:41 < jtanx> and some other crap +15:41 < jtanx> I was running guvcview though +15:42 < jtanx> and it seemed to take 640x480 just fine +15:42 < jtanx> it was quite slow with x-forwarding and all, but at least it worked +15:44 < sam_moore> Hmm +15:44 < sam_moore> I would be inclined to say that taking a bunch of data points at low resolution is probably more useful than taking 2 data points at high resolution +15:45 < sam_moore> The best solution is to find some way of reading at the higher resolution though +15:46 < sam_moore> ... I'm worried about what Kieran is going to produce if he writes this dilatometer algorithm +15:46 < sam_moore> It's going to return an array isn't it... +15:46 < sam_moore> I have no idea why +15:47 < jtanx> :3 +15:47 < jtanx> brb ~5 mins while I relocate to a lecture theatre +15:47 < sam_moore> Ok +15:47 -!- jtanx [~asfa@130.95.248.194] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"] +15:54 -!- jtanx [~asfa@130.95.248.194] has joined #mctxuwa_softdev +16:13 < sam_moore> Ok, this is both terrible and awesome +16:13 < sam_moore> But I've written a test "sensor" that is essentially just an external program +16:17 < sam_moore> So... if anyone complains about not being able to use python... they can use python +16:17 < sam_moore> And they don't have to reinvent all of our FastCGI stuff +16:17 < sam_moore> Although apparently that's easy in python anyway +16:17 < sam_moore> It's the thought that counts +16:21 < jtanx> :P +16:21 < jtanx> flup +16:30 < jtanx> http://zacharyvoase.com/2009/09/08/sendfile/ +16:30 < jtanx> this could be interesting +16:31 < jtanx> replace static with our fastcgi app +16:31 < jtanx> http://wiki.nginx.org/XSendfile +17:00 -!- jtanx [~asfa@130.95.248.194] has quit [Ping timeout] +17:52 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has joined #mctxuwa_softdev +18:07 < jtanx> mmm feature creep +18:08 < sam_moore> Meh, it was better than dealing with that switch statement +18:09 < jtanx> yeah +18:09 < sam_moore> Also it made it really easy to decide where to put mutexes +18:10 -!- MctxBot [~twang@106-68-47-96.dyn.iinet.net.au] has quit [Ping timeout] +18:10 < sam_moore> Given that I would have had to write a "strain.c" and do about the same amount of work anyway, I think the solution is a good one +18:10 < jtanx> it kind o makes sense though +18:11 < jtanx> you can't particularly generalise that much to each sensor type +18:11 < jtanx> so it makes sense to separate out based on what type of sensor it is +18:11 < sam_moore> Yes +18:11 < sam_moore> The Piped sensor is definitely feature creep though :P +18:12 < jtanx> ^-^ +18:12 < sam_moore> Although... +18:13 < sam_moore> If we wanted to distribute the sensors with a "master slave" system +18:13 < sam_moore> The code would end up looking alot like that +18:13 < sam_moore> Except with a network socket instead of a unix domain socket +18:14 < sam_moore> Also I'm not sure why I called it piped given that it doesn't use a pipe +18:14 < sam_moore> I suppose it sounds cooler than "uds" +18:20 < jtanx> So with electronics +18:20 < jtanx> the reason why they were having issues with getting the psu approved +18:20 < jtanx> was because they wanted to cut the leads and solder directly to the board +18:21 < jtanx> Oliver suggested that they use extension connectors +18:21 < jtanx> and cut the lead on the extension +18:21 < jtanx> but why they didn't think of that first, I don't know +18:37 -!- MctxBot [~twang@106-68-47-96.dyn.iinet.net.au] has joined #mctxuwa_softdev +18:45 < jtanx> now to watch this week's lecture... +21:31 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"] +--- Day changed Thu Oct 10 2013 +08:11 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has joined #mctxuwa_softdev +09:40 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"] +13:30 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has joined #mctxuwa_softdev +23:04 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"] +--- Day changed Fri Oct 11 2013 +08:21 -!- Rowan [~Rowan@130.95.129.225] has joined #mctxuwa_softdev +08:42 -!- Rowan [~Rowan@130.95.129.225] has quit [EOF From client] +09:19 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has joined #mctxuwa_softdev +09:59 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"] +15:09 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has joined #mctxuwa_softdev +15:10 < jtanx> urgh +15:10 < jtanx> burning the midnight oil trying to get my cits2232 project done +15:10 < jtanx> but +15:10 < jtanx> it has taught me a lot about django +15:10 < jtanx> which may be of use, especially for the whole admin stuff +15:11 < jtanx> one possibility is that we have two separate 'logins' +15:11 < jtanx> one gains you access to the site +15:11 < jtanx> one gains control over the bbb (eg the server api) +15:12 < jtanx> what you could do is keep the 'bind' functionality for the api, but make it check against the django user database for credentials +15:12 < jtanx> I'll look into it, if not today then probably tomorrow +19:39 < sam_moore> That sounds good +19:48 < sam_moore> Should we do something like this: http://stackoverflow.com/questions/8988855/include-another-html-file-in-a-html-file (The accepted answer) to do with our sidebar/menu stuff +19:48 < sam_moore> Although... +19:48 < jtanx> well +19:48 < jtanx> if we use django it has really cool templating stuff +19:48 < sam_moore> That solution is actually about as much as just copy/pasting the sidebar at the moment +19:48 < jtanx> yeah +19:49 < sam_moore> So Django does parts of our GUI as well as the user management system? +19:49 < jtanx> Well yeah +19:49 < jtanx> so django would be ui mostly +19:49 < jtanx> then tack on to that our api +19:49 < jtanx> which remains exactly the same +19:49 < jtanx> except that it uses the django database for checking authorizaiton +19:50 < jtanx> so right before you commence an experiment, you try to 'gain control' of the bbb, you resupply your login creds +19:50 < sam_moore> Sure, that's a good solution, I thought django would just do user management though (rather than replace the JavaScript GUI) +19:50 < jtanx> well if you do use django +19:50 < jtanx> it's a good idea to change some of it, because of the templating system +19:51 < jtanx> and that it can conditionally show content +19:51 < sam_moore> Ok +19:51 < jtanx> some of the javascript stuff will stay though, definitely +19:51 < jtanx> especially all the live update stuff +19:51 < jtanx> but this is getting ahead of myself, because i'm not even sure if this will work +19:51 < sam_moore> Hahaha +19:52 < sam_moore> You're entering a territory where I can't help much +19:52 < jtanx> yeah, well all of this 'web development' is new area for me too :P +19:52 < jtanx> I'm supposed to be majoring in computation, not web tech... +19:52 < sam_moore> You know more/better jQuery than me +19:52 < jtanx> jquery is pretty easy to learn though +19:53 < jtanx> i picked it up completely from this project +19:53 < sam_moore> Well, that's one reason why we went with it, since I used it briefly for something and it wasn't too hard, and James said he'd used it before too +19:54 < jtanx> Yeah +19:54 < sam_moore> If you want to replace stuff with django, I'm ok with that, if it's simpler +19:55 < jtanx> I'll see how it goes, hopefully it shouldn't be too hard to setup +19:55 < sam_moore> It just seems silly to copy/paste this Navigation Menu into every file +19:55 < jtanx> yep +19:55 < sam_moore> But copy/pasting the code (stack overflow) to automatically make it is also annoying +19:55 < jtanx> We can roll with the jquery soln if I don't get this working +19:56 < sam_moore> Unless you could include that in the "runBeforeLoad()" ? +19:56 < jtanx> can't you just have a placeholder +19:56 < jtanx> and on document load, you load it? +19:56 < sam_moore> Yeah +19:56 < jtanx> It'll look a bit crap because you'll see it before the sidebar loads +19:56 < sam_moore> Actually that's how you're doing other things +19:56 < jtanx> yeah +19:56 < sam_moore> But the navigation menu is hard coded html +19:56 < sam_moore> I'm just reading the existing code :P +19:56 < jtanx> haha +19:57 < jtanx> yeah it's hardcoded - it's the easiest solution +19:57 < jtanx> I've got my case study for 2402 this/next week too +19:57 < sam_moore> Um... I think it's fairly easy to call load from a file +19:57 < sam_moore> I'll look into that +19:57 < jtanx> ok +19:58 < sam_moore> The case study was reasonable, although he wanted a lot more detail from us +19:58 < jtanx> what did you do? +19:58 < sam_moore> Despite struggling to cram it into 4 pages +19:58 < sam_moore> The pencil +19:58 < sam_moore> That's a good one +19:58 < jtanx> ah +19:58 < sam_moore> Lots of youtube videos +19:58 < jtanx> we're doing pet bottles +19:58 < jtanx> there's this megafactories one on the coke plant which was prettyc ool +19:58 < jtanx> but yeah the pencil one also has a lot +19:58 < jtanx> i think i saw the how its made one +19:59 < jtanx> Ha +19:59 < jtanx> http://wiki.nginx.org/HttpSsiModule +19:59 < jtanx> if you want to get server specific +19:59 < jtanx> you can do ssi +19:59 < jtanx> let's not do that if possible :P +20:00 < sam_moore> Wait... +20:00 < sam_moore> It looks like you put html comments and they get sent to the server? +20:00 < sam_moore> How is that a thing? +20:00 < jtanx> +20:00 < jtanx> nah what happens +20:00 < jtanx> is the server reads the html file +20:00 < jtanx> and where there's special placeholders +20:00 < jtanx> it does stuff +20:00 < sam_moore> Ah +20:01 < sam_moore> No, do it client side +20:01 < jtanx> yeah +20:01 < sam_moore> There's like jQuery.load +20:01 < sam_moore> I'd probably keep django for user auth to start with +20:01 < sam_moore> But I think jQuery should be sufficient for the GUI +20:02 < jtanx> I think if you roll with django +20:02 < jtanx> you should use jQuery for all the interactive stuff +20:02 < jtanx> (which you'd have to anyway) +20:02 < jtanx> but you should use the templating system that django's got +20:03 < sam_moore> Ok, what does django give you exactly then, I'm not quite sure what the "template" stuff is, I assume it's nothing like a C++ template +20:03 < jtanx> OK +20:03 < jtanx> hmm +20:03 < jtanx> I'll give you access to my cits2232 repo temporarily +20:03 < jtanx> so you can see +20:03 < sam_moore> If it lets you easily include bits of html that's cool, but jQuery looks to be able to do that easily as well +20:03 < sam_moore> Alright +20:04 < jtanx> actually you know what +20:04 < jtanx> i'll just post it on pastebin or something +20:05 < jtanx> http://privatepaste.com/ec22ba7238 +20:05 < jtanx> That's the base template +20:05 < jtanx> http://privatepaste.com/07499e4a19 +20:05 < jtanx> That's the index +20:05 < jtanx> {{variable_name}} will display a variable name +20:05 < jtanx> sorry, the contents of that variable +20:06 < jtanx> {% %} blocks are for control +20:06 < sam_moore> Hmm +20:07 < jtanx> the base template got a bit out of hand for that project +20:07 < sam_moore> Well, if you want to use something like that I'm ok with it +20:07 < sam_moore> Although I think the thing that I was originally complaining about can be solved in jQuery :P +20:08 < jtanx> yeah +20:08 < sam_moore> But you should probably ask everyone else involved with the GUI for input too +20:08 < jtanx> Yeah, good point +20:09 < sam_moore> ... Really I didn't want to be involved with the GUI, but I kind of need to mess around with at least some basic graph stuff to work out if I need to change the server sensors/actuators api +20:09 < jtanx> well let's just say that i did not envision doing the gui either +20:09 < sam_moore> I'll try and keep what I do consistent with the overall style though +20:09 < sam_moore> Which is how I got sidetracked complaining about copy/pasting things :P +20:10 < sam_moore> I think the GUI we have so far is pretty good +20:10 < jtanx> hehehe +20:10 < jtanx> yeah it's not too bad +20:11 < jtanx> the only thing that I wouldn't know how to do is that admin stuff +20:11 < sam_moore> user admin or experiment admin? +20:11 < jtanx> um +20:11 < jtanx> I dunno, whatever that 'admin' functionality needs to be +20:11 < jtanx> like 'access the full logs' +20:11 < sam_moore> Oh, right +20:12 < jtanx> unless you start implementing permissions in the api +20:12 < sam_moore> Hmm, we might have to +20:13 < sam_moore> It should just be an enum for the user type that gets returned/set by the authentication handler and checked for certain operations +20:13 < sam_moore> But as you mentioned the other day, you don't want someone to be able to login after someone else has started an experiment and cancel it +20:13 < sam_moore> Actually +20:13 < sam_moore> Do you? +20:13 < jtanx> admin only? +20:13 < sam_moore> Exactly +20:14 < jtanx> how difficult is that to do though +20:14 < sam_moore> Store the username of who owns the current experiment +20:14 < sam_moore> Store an enum of the user type of whoever is currently logged in +20:14 < sam_moore> If someone tries to stop the experiment, unless their username matches, or they are an admin, refuse +20:14 < sam_moore> There could be a safety issue there? +20:15 < jtanx> the only thing is +20:15 < sam_moore> If someone needs to stop the experiment but isn't an admin? +20:15 < jtanx> everything's based off the control key +20:15 < sam_moore> Meh... add another cookie +20:15 < sam_moore> ... That escalated quickly +20:15 < jtanx> you've already used the nameless cookie +20:15 < jtanx> if you add another cookie +20:15 < jtanx> you have to do string parsing +20:15 < jtanx> and have named cookies +20:16 < sam_moore> Hmmm +20:16 < sam_moore> "Beyond the scope of the project" ? +20:16 < jtanx> i think the format was +20:16 < jtanx> key=value; key=value2 +20:16 < jtanx> yeah, beyond the scope :P +20:16 < sam_moore> Yeah, that is ever so annoying +20:16 < jtanx> let's leave it for now +20:16 < jtanx> come back to it +20:16 < sam_moore> Because if it were something like "key=value&key=value2" you could potentially reuse the FCGI parser +20:17 < jtanx> yeah +20:17 < jtanx> you could modify it +20:17 < sam_moore> That would be the way to go +20:17 < jtanx> generalise it +20:17 < sam_moore> But it's low priority now +20:17 < jtanx> yep +20:17 < jtanx> wow i'm getting seriously sidetracked +20:18 < jtanx> this mechatronics project is more interesting than any of my other projects though +20:18 < sam_moore> Haha +20:19 < sam_moore> I know what you mean +20:19 < sam_moore> I want to compile a RT linux kernel on my laptop (since they have x86 versions) +20:19 < sam_moore> So that I can look at how much the consistency in sample rate could theoretically improve... +20:20 < sam_moore> If someone managed to port the RT linux kernel to the BeagleBone +20:20 < jtanx> RT linux on your computer +20:20 < jtanx> isn't that generally bad +20:20 < jtanx> because RT screws up other stuff +20:21 < sam_moore> Like what? +20:21 < jtanx> decreased performance to meet the deadlines +20:21 < sam_moore> Also, I don't have to permanently replace my existing kernel, just add another bootloader entry +20:22 < sam_moore> If it screws up nginx that would be annoying though +20:23 < jtanx> while you're at it, try running the bfs scheduler instead of the cfs scheduler :P +20:23 < sam_moore> Maybe +20:42 < sam_moore> Ok, the solution to the copy/paste is just something like $("#sidebar").load("static/sidebar.html") +20:42 < sam_moore> However, whilst running this in the document.ready function on a test page works, running it in index.html appears to have no effect +20:43 < sam_moore> Oh +20:43 < sam_moore> I know why +20:43 < sam_moore> Because runBeforeLoad is failing +20:43 < sam_moore> Presumably you still want to load content like the navigation bar even if the server isn't running +20:44 < sam_moore> So... there is ".done()" and ".fail()" ... is there ".either()"? +20:46 < sam_moore> Ahahaha +20:47 < sam_moore> Of course, there is ".always()" +20:49 < sam_moore> Hmm, I wouldn't want to design any sort of large system with a language like this, but it is an interesting style +20:51 < jtanx> haha +20:52 < sam_moore> So, a lot of the header stuff can be moved into .html files and just jQuery.loaded, which should make things cleaner +20:52 < sam_moore> I will refrain from doing that now though +20:53 < jtanx> don't be too tempted to do a lot of templating with javascript +20:53 < jtanx> because it will load asynchronously, stuff will look weird +20:53 < jtanx> and if the js breaks +20:53 < jtanx> well, your page is more screwed than before +20:54 < sam_moore> Ok... but you could still potentially have at least one html file in the static directory +20:54 < sam_moore> With all the header and navigation stuff +20:55 < sam_moore> Just have a .fail() function! +20:55 < sam_moore> That's what it's for, right :P +20:56 < sam_moore> $("#thing").load("static/thing.html").fail($("#thing").html("

Something fucked up

") +20:56 < sam_moore> Oh dear +20:58 < jtanx> ~.~ +20:58 < jtanx> django here: https://mctx.us.to:8043/databases +20:58 < jtanx> login with testuser:testuser +20:58 < jtanx> ignore that it looks a lot like our gui +20:58 < jtanx> except it doesnt work anymore +21:00 < sam_moore> Well, django's used for UCC for member administration +21:01 < sam_moore> Except it's entirely seperate from LDAP which we use for authentication +21:01 < sam_moore> Except it's not really because it binds to LDAP for authentication :S +21:01 < sam_moore> Wheels within wheels... +21:02 < jtanx> ookay +21:04 < sam_moore> You need LDAP for shell access +21:04 < sam_moore> In fact... +21:04 < sam_moore> We could potentially give pheme users shell access to the BeagleBone +21:04 < sam_moore> But there's not really any point +21:04 < jtanx> is that a good idea +21:05 < sam_moore> No +21:05 < sam_moore> It's an awful idea +21:05 < sam_moore> Just saying it's possible :P +21:06 < sam_moore> Someone could do this: :(){ :|:& };: +21:06 < sam_moore> And break the entire thing +21:08 < jtanx> is that the fork bomb +21:09 < jtanx> ok the site works now +21:09 < jtanx> sort of +21:10 < sam_moore> 400 Bad Request +21:10 < jtanx> hurr +21:10 < sam_moore> I think iceweasel is being dumb and ignoring https:// ? +21:10 < sam_moore> Although it was fine before +21:11 < jtanx> nah probably screwed up the nginx config +21:11 < sam_moore> No, it works, I just had to type the https:// instead of copy paste +21:11 < sam_moore> Wierd +21:11 < sam_moore> The test login doesn't work though +21:11 < jtanx> testuser doesn't work for some reason +21:11 < jtanx> yeah just register +21:12 < sam_moore> Will it actually send an email? +21:12 < jtanx> urgh +21:12 < jtanx> no +21:12 < sam_moore> haha +21:12 < jtanx> okay no the config is still broken +21:16 < sam_moore> I'll have to come back to this tomorrow +21:16 < sam_moore> Bye +21:17 < jtanx> ok bye +23:09 -!- jtanx [~asfa@106-68-47-96.dyn.iinet.net.au] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"] diff --git a/server/Makefile b/server/Makefile index f3992c8..c5be05f 100644 --- a/server/Makefile +++ b/server/Makefile @@ -2,13 +2,17 @@ CXX = gcc FLAGS = -std=c99 -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 +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 BIN = server #BIN2 = stream -all : $(BIN) $(BIN2) +all : + $(MAKE) -C sensors + $(MAKE) -C actuators + $(MAKE) $(BIN) + $(BIN) : $(OBJ) @@ -19,12 +23,13 @@ $(BIN) : $(OBJ) $(CXX) $(FLAGS) -c $< - clean : + make -C sensors clean $(RM) $(BIN) $(BIN2) $(RM) *.o clean_full: #cleans up all backup files + make -C sensors clean_full $(RM) $(BIN) $(BIN2) $(OBJ) $(LINKOBJ) $(RM) *.*~ $(RM) *~ diff --git a/server/actuator.c b/server/actuator.c index f5d7cd6..3e26e37 100644 --- a/server/actuator.c +++ b/server/actuator.c @@ -9,30 +9,52 @@ #include "bbb_pin.h" + + +/** Number of actuators **/ +int g_num_actuators = 0; + /** Array of Actuators (global to this file) initialised by Actuator_Init **/ -static Actuator g_actuators[NUMACTUATORS]; +static Actuator g_actuators[ACTUATORS_MAX]; +/** + * Add and initialise an Actuator + * @param name - Human readable name of the actuator + * @param read - Function to call whenever the actuator should be read + * @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) +{ + if (++g_num_actuators > ACTUATORS_MAX) + { + Fatal("Too many sensors; Increase ACTUATORS_MAX from %d in actuator.h and recompile", ACTUATORS_MAX); + } + Actuator * a = &(g_actuators[g_num_actuators-1]); + a->id = g_num_actuators-1; + a->user_id = user_id; + Data_Init(&(a->data_file)); + 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); + + return g_num_actuators; +} -/** Human readable names for the Actuators **/ -const char * g_actuator_names[NUMACTUATORS] = { - "actuator_test0", "gpio1_16", "EHRPWM0A_duty@60Hz" -}; /** * One off initialisation of *all* Actuators */ +#include "actuators/ledtest.h" +#include "actuators/filetest.h" void Actuator_Init() { - for (int i = 0; i < NUMACTUATORS; ++i) - { - g_actuators[i].id = i; - Data_Init(&(g_actuators[i].data_file)); - pthread_mutex_init(&(g_actuators[i].mutex), NULL); - } - - // Initialise pins used - GPIO_Export(GPIO1_16); - PWM_Export(EHRPWM0A); - + //Actuator_Add("ledtest",0, Ledtest_Set, NULL,NULL,NULL); + Actuator_Add("filetest", 0, Filetest_Set, Filetest_Init, Filetest_Cleanup, Filetest_Sanity); } /** @@ -112,7 +134,7 @@ void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg) */ void Actuator_SetModeAll(ControlModes mode, void * arg) { - for (int i = 0; i < NUMACTUATORS; i++) + for (int i = 0; i < ACTUATORS_MAX; i++) Actuator_SetMode(&g_actuators[i], mode, arg); } @@ -138,7 +160,23 @@ void * Actuator_Loop(void * arg) if (!a->activated) break; - Actuator_SetValue(a, a->control.value); + Actuator_SetValue(a, a->control.start); + // Currently does discrete steps after specified time intervals + while (a->control.steps > 0 && a->activated) + { + usleep(1e6*(a->control.stepwait)); + a->control.start += a->control.stepsize; + Actuator_SetValue(a, a->control.start); + + a->control.steps--; + } + usleep(1e6*(a->control.stepwait)); + + //TODO: + // Note that although this loop has a sleep in it which would seem to make it hard to enforce urgent shutdowns, + // You can call the Actuator's cleanup function immediately (and this loop should later just exit) + // tl;dr This function isn't/shouldn't be responsible for the emergency Actuator stuff + // (That should be handled by the Fatal function... at some point) } //TODO: Cleanup? @@ -170,53 +208,21 @@ void Actuator_SetControl(Actuator * a, ActuatorControl * c) */ void Actuator_SetValue(Actuator * a, double value) { + if (a->sanity != NULL && !a->sanity(a->user_id, value)) + { + //ARE YOU INSANE? + Fatal("Insane value %lf for actuator %s", value, a->name); + } + if (!(a->set(a->user_id, value))) + { + Fatal("Failed to set actuator %s to %lf", a->name, value); + } + // Set time stamp struct timeval t; gettimeofday(&t, NULL); - + // Record and save DataPoint DataPoint d = {TIMEVAL_DIFF(t, *Control_GetStartTime()), value}; - //TODO: Set actuator - switch (a->id) - { - case ACTUATOR_TEST0: - { - // Onboard LEDs test actuator - FILE *led_handle = NULL; //code reference: http://learnbuildshare.wordpress.com/2013/05/19/beaglebone-black-controlling-user-leds-using-c/ - const char *led_format = "/sys/class/leds/beaglebone:green:usr%d/brightness"; - char buf[50]; - bool turn_on = value; - - for (int i = 0; i < 4; i++) - { - snprintf(buf, 50, led_format, i); - if ((led_handle = fopen(buf, "w")) != NULL) - { - if (turn_on) - fwrite("1", sizeof(char), 1, led_handle); - else - fwrite("0", sizeof(char), 1, led_handle); - fclose(led_handle); - } - else - Log(LOGDEBUG, "LED fopen failed: %s", strerror(errno)); - } - } - break; - case ACTUATOR_TEST1: - GPIO_Set(GPIO1_16, (bool)(value)); - break; - case ACTUATOR_TEST2: - { - // PWM analogue actuator (currently generates one PWM signal with first PWM module) - static long freq = 16666666; // This is 60Hz - PWM_Set(EHRPWM0A, true, freq, value * freq); // Set the duty cycle - break; - } - } - - Log(LOGDEBUG, "Actuator %s set to %f", g_actuator_names[a->id], value); - - // Record the value Data_Save(&(a->data_file), &d, 1); } @@ -226,15 +232,16 @@ void Actuator_SetValue(Actuator * a, double value) * @param format - Format * @param id - ID of Actuator */ -void Actuator_BeginResponse(FCGIContext * context, ActuatorId id, DataFormat format) +void Actuator_BeginResponse(FCGIContext * context, Actuator * a, DataFormat format) { // Begin response switch (format) { case JSON: FCGI_BeginJSON(context, STATUS_OK); - FCGI_JSONLong("id", id); - FCGI_JSONPair("name", g_actuator_names[id]); + FCGI_JSONLong("id", a->id); + FCGI_JSONLong("user_id", a->user_id); //TODO: Don't need to show this? + FCGI_JSONPair("name", a->name); break; default: FCGI_PrintRaw("Content-type: text/plain\r\n\r\n"); @@ -248,7 +255,7 @@ void Actuator_BeginResponse(FCGIContext * context, ActuatorId id, DataFormat for * @param id - ID of the Actuator * @param format - Format */ -void Actuator_EndResponse(FCGIContext * context, ActuatorId id, DataFormat format) +void Actuator_EndResponse(FCGIContext * context, Actuator * a, DataFormat format) { // End response switch (format) @@ -273,15 +280,17 @@ void Actuator_Handler(FCGIContext * context, char * params) gettimeofday(&now, NULL); double current_time = TIMEVAL_DIFF(now, *Control_GetStartTime()); int id = 0; - double set = 0; + char * name = ""; + char * set = ""; double start_time = 0; double end_time = current_time; char * fmt_str; // key/value pairs FCGIValue values[] = { - {"id", &id, FCGI_REQUIRED(FCGI_INT_T)}, - {"set", &set, FCGI_DOUBLE_T}, + {"id", &id, FCGI_INT_T}, + {"name", &name, FCGI_STRING_T}, + {"set", &set, FCGI_STRING_T}, {"start_time", &start_time, FCGI_DOUBLE_T}, {"end_time", &end_time, FCGI_DOUBLE_T}, {"format", &fmt_str, FCGI_STRING_T} @@ -290,6 +299,7 @@ void Actuator_Handler(FCGIContext * context, char * params) // enum to avoid the use of magic numbers typedef enum { ID, + NAME, SET, START_TIME, END_TIME, @@ -305,34 +315,100 @@ void Actuator_Handler(FCGIContext * context, char * params) // Get the Actuator identified Actuator * a = NULL; - if (id < 0 || id >= NUMACTUATORS) + + if (FCGI_RECEIVED(values[NAME].flags)) + { + if (FCGI_RECEIVED(values[ID].flags)) + { + FCGI_RejectJSON(context, "Can't supply both id and name"); + return; + } + a = Actuator_Identify(name); + if (a == NULL) + { + FCGI_RejectJSON(context, "Unknown actuator name"); + return; + } + + } + else if (!FCGI_RECEIVED(values[ID].flags)) + { + FCGI_RejectJSON(context, "No id or name supplied"); + return; + } + else if (id < 0 || id >= ACTUATORS_MAX) { FCGI_RejectJSON(context, "Invalid Actuator id"); return; } + else + { + a = &(g_actuators[id]); + } - a = g_actuators+id; DataFormat format = Data_GetFormat(&(values[FORMAT])); - // Begin response - Actuator_BeginResponse(context, id, format); - // Set? + + if (FCGI_RECEIVED(values[SET].flags)) { - if (format == JSON) - FCGI_JSONDouble("set", set); + - ActuatorControl c; - c.value = set; - + ActuatorControl c = {0.0, 0.0, 0.0, 0}; // Need to set default values (since we don't require them all) + // sscanf returns the number of fields successfully read... + int n = sscanf(set, "%lf,%lf,%lf,%d", &(c.start), &(c.stepwait), &(c.stepsize), &(c.steps)); // Set provided values in order + if (n != 4) + { + // 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); + } + // SANITY CHECKS + if (c.stepwait < 0 || c.steps < 0 || (a->sanity != NULL && !a->sanity(a->user_id, c.start))) + { + FCGI_RejectJSON(context, "Bad Actuator setting"); + return; + } Actuator_SetControl(a, &c); + } + + // Begin response + Actuator_BeginResponse(context, a, format); + if (format == JSON) + FCGI_JSONPair("set", set); // Print Data Data_Handler(&(a->data_file), &(values[START_TIME]), &(values[END_TIME]), format, current_time); // Finish response - Actuator_EndResponse(context, id, format); + Actuator_EndResponse(context, a, format); +} + +/** + * Get the name of an Actuator given its id + * @param id - ID of the actuator + * @returns The Actuator's name + */ +const char * Actuator_GetName(int id) +{ + return g_actuators[id].name; +} + +/** + * Identify an Actuator from its name string + * @param name - The name of the Actuator + * @returns Actuator + */ +Actuator * Actuator_Identify(const char * name) +{ + for (int i = 0; i < g_num_actuators; ++i) + { + if (strcmp(g_actuators[i].name, name) == 0) + return &(g_actuators[i]); + } + return NULL; } diff --git a/server/actuator.h b/server/actuator.h index 48e0f29..de81eea 100644 --- a/server/actuator.h +++ b/server/actuator.h @@ -8,37 +8,44 @@ #include "common.h" #include "data.h" +#include "device.h" -//NOTE: Functionality is very similar to Sensor stuff -// BUT it's probably very unwise to try and generalise Sensors and Actuators to the same thing (ie: Device) -// Might be OK in C++ but not easy in C -/** Number of actuators **/ -#define NUMACTUATORS 3 +/** + * Maximum number of actuators program can be compiled with + * (If you get an error "Increase ACTUATORS_MAX from %d" this is what it refers to) + */ +#define ACTUATORS_MAX 5 +extern int g_num_actuators; // in actuator.c -/** List of actuator ids (should be of size NUMACTUATORS) **/ -typedef enum -{ - ACTUATOR_TEST0, - ACTUATOR_TEST1, - ACTUATOR_TEST2 -} ActuatorId; -/** Human readable names for the Actuators **/ -extern const char * g_actuator_names[NUMACTUATORS]; /** Control structure for Actuator setting **/ typedef struct { //TODO: Add functionality as needed - /** Simple value for Actuator **/ - double value; + // Currently implements a simple piecewise step increase + // Would be cool to have a function specified as a string... eg: "1.0 + 0.5*s^2" with "s" the step number, and then give "stepwait" and "steps" + // ... But that, like so many things, is probably overkill + /** Current value of Actuator **/ + double start; + /** Time to maintain Actuator at each value **/ + double stepwait; + /** Amount to increase/decrease Actuator on each step **/ + double stepsize; + /** Number of steps still to perform **/ + int steps; // Note that after it is first set, this will be decremented until it is zero + } ActuatorControl; typedef struct { /** ID number of the actuator **/ - ActuatorId id; + int id; + /** User ID number **/ + int user_id; + /** Name **/ + const char * name; /** Control parameters for the Actuator **/ ActuatorControl control; /** Flag indicates if ActuatorControl has been changed **/ @@ -53,7 +60,15 @@ typedef struct pthread_cond_t cond; /** Indicates whether the Actuator is running **/ bool activated; - + /** Initialisation function **/ + InitFn init; + /** Set function **/ + SetFn set; + /** Sanity check function **/ + SanityFn sanity; + /** Cleanup function **/ + CleanFn clean; + } Actuator; extern void Actuator_Init(); // One off initialisation of *all* Actuators @@ -67,6 +82,7 @@ extern void Actuator_SetControl(Actuator * a, ActuatorControl * c); // Set the c extern Actuator * Actuator_Identify(const char * str); // Identify a Sensor from a string Id extern void Actuator_Handler(FCGIContext *context, char * params); // Handle a FCGI request for Actuator control +extern const char * Actuator_GetName(int id); #endif //_ACTUATOR_H diff --git a/server/actuators/Makefile b/server/actuators/Makefile new file mode 100644 index 0000000..a2ba5dc --- /dev/null +++ b/server/actuators/Makefile @@ -0,0 +1,27 @@ +# Makefile for server software +CXX = gcc +FLAGS = -std=c99 -Wall -pedantic -g -I../ +#-I/usr/include/opencv -I/usr/include/opencv2/highgui For OpenCV +LIB = -lpthread +OBJ = ledtest.o filetest.o +HEADERS = $(wildcard *.h) +RM = rm -f + +all : $(OBJ) + ar rvs actuators.a $(OBJ) + + +%.o : %.c + $(CXX) $(FLAGS) -c $< + +clean : + $(RM) $(BIN) + $(RM) *.o + +clean_full: #cleans up all backup files + $(RM) $(BIN) $(OBJ) + $(RM) *.*~ + $(RM) *~ + + + diff --git a/server/actuators/filetest.c b/server/actuators/filetest.c new file mode 100644 index 0000000..697753a --- /dev/null +++ b/server/actuators/filetest.c @@ -0,0 +1,25 @@ +#include "filetest.h" + +static FILE * f = NULL; +bool Filetest_Init(const char * name, int id) +{ + f = fopen(name, "w"); + setbuf(f, NULL); // Unbuffer + return (f != NULL); +} + +bool Filetest_Set(int id, double value) +{ + Log(LOGDEBUG, "Writing %lf to file", value); + return (fprintf(f, "%lf\n", value) > 1); +} + +bool Filetest_Cleanup(int id) +{ + return (fclose(f) == 0); +} + +bool Filetest_Sanity(int id, double value) +{ + return (abs(value) <= 1e4); +} diff --git a/server/actuators/filetest.h b/server/actuators/filetest.h new file mode 100644 index 0000000..a389c99 --- /dev/null +++ b/server/actuators/filetest.h @@ -0,0 +1,13 @@ +#ifndef _FILETEST_H +#define _FILETEST_H + +#include "../common.h" + +extern bool Filetest_Init(const char * name, int id); +extern bool Filetest_Set(int id, double value); +extern bool Filetest_Cleanup(int id); +extern bool Filetest_Sanity(int id, double value); + +#endif //_FILETEST_H + + diff --git a/server/actuators/ledtest.c b/server/actuators/ledtest.c new file mode 100644 index 0000000..c2f366c --- /dev/null +++ b/server/actuators/ledtest.c @@ -0,0 +1,29 @@ +#include "ledtest.h" + +bool Ledtest_Set(int id, double value) +{ + + FILE *led_handle = NULL; //code reference: http://learnbuildshare.wordpress.com/2013/05/19/beaglebone-black-controlling-user-leds + const char *led_format = "/sys/class/leds/beaglebone:green:usr%d/brightness"; + char buf[50]; + bool turn_on = value; + + for (int i = 0; i < 4; i++) + { + snprintf(buf, 50, led_format, i); + if ((led_handle = fopen(buf, "w")) != NULL) + { + if (turn_on) + fwrite("1", sizeof(char), 1, led_handle); + else + fwrite("0", sizeof(char), 1, led_handle); + fclose(led_handle); + } + else + { + Log(LOGDEBUG, "LED fopen failed: %s", strerror(errno)); + return false; + } + } + return true; +} diff --git a/server/actuators/ledtest.h b/server/actuators/ledtest.h new file mode 100644 index 0000000..46ce7a4 --- /dev/null +++ b/server/actuators/ledtest.h @@ -0,0 +1,11 @@ +#ifndef _LED_TEST_H +#define _LED_TEST_H + +#include "../common.h" + +extern bool Ledtest_Set(int id, double value); + + +#endif //_LED_TEST_H + +//EOF diff --git a/server/control.c b/server/control.c index 4b4a1e6..820c37c 100644 --- a/server/control.c +++ b/server/control.c @@ -32,13 +32,19 @@ bool PathExists(const char *path) * @param params The input parameters */ void Control_Handler(FCGIContext *context, char *params) { - const char *action, *key = "", *name = ""; + const char *action = ""; + 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] = { {"action", &action, FCGI_REQUIRED(FCGI_STRING_T)}, - {"key", &key, FCGI_STRING_T}, {"force", &force, FCGI_BOOL_T}, {"name", &name, FCGI_STRING_T} }; @@ -51,27 +57,22 @@ void Control_Handler(FCGIContext *context, char *params) { return; } else if (!strcmp(action, "emergency")) { desired_mode = CONTROL_EMERGENCY; - } else if (FCGI_HasControl(context, key)) { - if (!strcmp(action, "release")) { - FCGI_ReleaseControl(context); - } else if (!strcmp(action, "start")) { - desired_mode = CONTROL_START; - } else if (!strcmp(action, "pause")) { - desired_mode = CONTROL_PAUSE; - } else if (!strcmp(action, "resume")) { - desired_mode = CONTROL_RESUME; - } else if (!strcmp(action, "stop")) { - desired_mode = CONTROL_STOP; - } else { - FCGI_RejectJSON(context, "Unknown action specified."); - return; - } + } + else if (!strcmp(action, "release")) { + FCGI_ReleaseControl(context); + } else if (!strcmp(action, "start")) { + desired_mode = CONTROL_START; + } else if (!strcmp(action, "pause")) { + desired_mode = CONTROL_PAUSE; + } else if (!strcmp(action, "resume")) { + desired_mode = CONTROL_RESUME; + } else if (!strcmp(action, "stop")) { + desired_mode = CONTROL_STOP; } else { - FCGI_RejectJSONEx(context, STATUS_UNAUTHORIZED, - "Invalid control key specified."); + FCGI_RejectJSON(context, "Unknown action specified."); return; } - + void *arg = NULL; if (desired_mode == CONTROL_START) { if (PathExists(name) && !force) { diff --git a/server/data.h b/server/data.h index e8d887f..df16efe 100644 --- a/server/data.h +++ b/server/data.h @@ -53,4 +53,5 @@ extern int Data_FindByTime(DataFile * df, double time_stamp, DataPoint * closest extern void Data_Handler(DataFile * df, FCGIValue * start, FCGIValue * end, DataFormat format, double current_time); // Helper; given FCGI params print data extern DataFormat Data_GetFormat(FCGIValue * fmt); // Helper; convert human readable format string to DataFormat + #endif //_DATAPOINT_H diff --git a/server/device.h b/server/device.h new file mode 100644 index 0000000..28b2406 --- /dev/null +++ b/server/device.h @@ -0,0 +1,19 @@ +/** + * @file device.h + * @brief Declare code/typedefs common to both Sensors and Actuators + */ +#ifndef _DEVICE_H +#define _DEVICE_H + +/** Function pointer for sensor reading **/ +typedef bool (*ReadFn)(int, double *); +/** Function pointer for actuator setting **/ +typedef bool (*SetFn)(int, double); +/** Function pointer for sensor initialisation **/ +typedef bool (*InitFn)(const char *, int); +/** Function pointer for sensor cleanup **/ +typedef bool (*CleanFn)(int); +/** Function to check the sanity of a value **/ +typedef bool (*SanityFn)(int, double); + +#endif //_DEVICE_H diff --git a/server/fastcgi.c b/server/fastcgi.c index 3218a58..fe461b8 100644 --- a/server/fastcgi.c +++ b/server/fastcgi.c @@ -52,22 +52,22 @@ static void IdentifyHandler(FCGIContext *context, char *params) { if (ident_sensors) { FCGI_JSONKey("sensors"); FCGI_JSONValue("{\n\t\t"); - for (i = 0; i < NUMSENSORS; i++) { + for (i = 0; i < g_num_sensors; i++) { if (i > 0) { FCGI_JSONValue(",\n\t\t"); } - FCGI_JSONValue("\"%d\" : \"%s\"", i, g_sensor_names[i]); + FCGI_JSONValue("\"%d\" : \"%s\"", i, Sensor_GetName(i)); } FCGI_JSONValue("\n\t}"); } if (ident_actuators) { FCGI_JSONKey("actuators"); FCGI_JSONValue("{\n\t\t"); - for (i = 0; i < NUMACTUATORS; i++) { + for (i = 0; i < g_num_actuators; i++) { if (i > 0) { FCGI_JSONValue(",\n\t\t"); } - FCGI_JSONValue("\"%d\" : \"%s\"", i, g_actuator_names[i]); + FCGI_JSONValue("\"%d\" : \"%s\"", i, Actuator_GetName(i)); } FCGI_JSONValue("\n\t}"); } @@ -507,7 +507,8 @@ void * FCGI_RequestLoop (void *data) if (module_handler) { - if (module_handler != Login_Handler && module_handler != IdentifyHandler) + //if (module_handler != Login_Handler && module_handler != IdentifyHandler) + if (false) // Testing { if (cookie[0] == '\0') { diff --git a/server/main.c b/server/main.c index ecdf068..c2e5161 100644 --- a/server/main.c +++ b/server/main.c @@ -16,6 +16,7 @@ #include // for system logging #include // for signal handling + // --- Variable definitions --- // Options g_options; // options passed to program through command line arguments @@ -96,30 +97,15 @@ void ParseArguments(int argc, char ** argv) } -/** - * Handle a signal - * @param signal - The signal number - */ -//TODO: Something that gets massively annoying with threads is that you can't predict which one gets the signal -// There are ways to deal with this, but I can't remember them -// Probably sufficient to just call Thread_QuitProgram here -void SignalHandler(int signal) -{ - // At the moment just always exit. - // Call `exit` so that Cleanup will be called to... clean up. - Log(LOGWARN, "Got signal %d (%s). Exiting.", signal, strsignal(signal)); - - //exit(signal); -} - /** * Cleanup before the program exits */ void Cleanup() { Log(LOGDEBUG, "Begin cleanup."); + Sensor_Cleanup(); + //Actuator_Cleanup(); Log(LOGDEBUG, "Finish cleanup."); - } /** @@ -135,33 +121,26 @@ int main(int argc, char ** argv) openlog("mctxserv", LOG_PID | LOG_PERROR, LOG_USER); Log(LOGINFO, "Server started"); - ParseArguments(argc, argv); - - //Open the system log + ParseArguments(argc, argv); // Setup the g_options structure from program arguments - // signal handler - //TODO: Make this work - /* - int signals[] = {SIGINT, SIGSEGV, SIGTERM}; - for (int i = 0; i < sizeof(signals)/sizeof(int); ++i) - { - signal(signals[i], SignalHandler); - } - */ Sensor_Init(); Actuator_Init(); Pin_Init(); - //Sensor_StartAll("test"); - //Actuator_StartAll("test"); + + // 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(); diff --git a/server/parameters b/server/parameters index 6ac1bc1..2fa6890 100644 --- a/server/parameters +++ b/server/parameters @@ -25,8 +25,8 @@ pin_test="0" auth_uri="/etc/shadow" # Set to the dn of the LDAP server -#ldap_base_dn="ou=People,dc=daedalus" # Testing -ldap_base_dn="ou=Users,ou=UWA,dc=uwads,dc=uwa,dc=edu,dc=au" #UWA +ldap_base_dn="ou=People,dc=daedalus" # Testing +#ldap_base_dn="ou=Users,ou=UWA,dc=uwads,dc=uwa,dc=edu,dc=au" #UWA ## OPTIONS TO BE PASSED TO SERVER; DO NOT EDIT diff --git a/server/run.sh b/server/run.sh index 0e52fd9..76a7a48 100755 --- a/server/run.sh +++ b/server/run.sh @@ -10,9 +10,9 @@ fi if [[ "$(uname -m)" != *arm* ]]; then echo Not running on the BBB # Use this to quickly test run the server in valgrind - spawn-fcgi -p9005 -n ./valgrind.sh + #spawn-fcgi -p9005 -n ./valgrind.sh # Use this to run the server normally - #spawn-fcgi -p9005 -n ./server + spawn-fcgi -p9005 -n ./server exit 0 fi @@ -83,6 +83,9 @@ echo "Parameters are: $parameters" # Run the program with parameters # TODO: Can tell spawn-fcgi to run the program as an unprivelaged user? # But first will have to work out how to set PWM/GPIO as unprivelaged user +# NOTE: Having the program automatically restart itself after a *FATAL ERROR* doesn't seem like such a good idea now +# (Some things that call Fatal happen under circumstances where they will just keep calling Fatal every time the program starts) +# Change the number of fails to 1 for now. We can potentially use different error codes for different types of errors, but that seems overkill. fails=0 while [ $fails -lt 1 ]; do spawn-fcgi -p9005 -n -- ./server $parameters diff --git a/server/sensor.c b/server/sensor.c index cfab514..792bbbd 100644 --- a/server/sensor.c +++ b/server/sensor.c @@ -11,67 +11,91 @@ #include /** Array of sensors, initialised by Sensor_Init **/ -static Sensor g_sensors[NUMSENSORS]; //global to this file - -/** Array of sensor threshold structures defining the safety values of each sensor**/ -const SensorThreshold thresholds[NUMSENSORS]= { - //Max Safety, Min safety, Max warning, Min warning - {5000,0,5000,0}, - {5000,0,5000,0}, - {5000,0,5000,0}, - {5000,0,5000,0}, - {5000,0,5000,0}, - {5000,0,5000,0}, - {5000,0,5000,0}, - {5000,0,5000,0}, - {1, 1, 1, 1} -}; - -/** Human readable names for the sensors **/ -const char * g_sensor_names[NUMSENSORS] = { - "strain0", - "strain1", - "strain2", - "strain3", - "pressure0", - "pressure1", - "pressure_feedback", - "microphone", - "enclosure" -}; +static Sensor g_sensors[SENSORS_MAX]; +/** The number of sensors **/ +int g_num_sensors = 0; + + + +/** + * Add and initialise a Sensor + * @param name - Human readable name of the sensor + * @param user_id - User identifier + * @param read - Function to call whenever the sensor should be read + * @param init - Function to call to initialise the sensor (may be NULL) + * @param max_error - Maximum error threshold; program will exit if this is exceeded for the sensor reading + * @param min_error - Minimum error threshold; program will exit if the sensor reading falls below this value + * @param max_warn - Maximum warning threshold; program will log warnings if the value exceeds this threshold + * @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) +{ + if (++g_num_sensors > SENSORS_MAX) + { + Fatal("Too many sensors; Increase SENSORS_MAX from %d in sensor.h and recompile", SENSORS_MAX); + // We could design the program to use realloc(3) + // But since someone who adds a new sensor has to recompile the program anyway... + } + Sensor * s = &(g_sensors[g_num_sensors-1]); + + s->id = g_num_sensors-1; + s->user_id = user_id; + Data_Init(&(s->data_file)); + 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; + s->averages = 1; + + // 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; + + return g_num_sensors; +} /** - * One off initialisation of *all* sensors + * Initialise all sensors used by the program + * TODO: Edit this to add any extra sensors you need + * TODO: Edit the includes as well */ +#include "sensors/resource.h" +#include "sensors/strain.h" +#include "sensors/piped.h" void Sensor_Init() { - for (int i = 0; i < NUMSENSORS; ++i) + 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("../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); + //Sensor_Add("strain2", STRAIN2, Strain_Read, Strain_Init, 5000,0,5000,0); + //Sensor_Add("strain3", STRAIN3, Strain_Read, Strain_Init, 5000,0,5000,0); + //Sensor_Add("pressure0", PRESSURE0, Pressure_Read, Pressure_Init, 5000,0,5000,0); + //Sensor_Add("pressure1", PRESSURE1, Pressure_Read, Pressure_Init, 5000,0,5000,0); + //Sensor_Add("pressure_feedback", PRESSURE_FEEDBACK, Pressure_Read, Pressure_Init, 5000,0,5000,0); + //Sensor_Add("enclosure", ENCLOSURE, Enclosure_Read, Enclosure_Init, 1,1,1,1); + //Sensor_Add("dilatometer", DILATOMETER, Dilatometer_Read, Dilatometer_Init, -1,-1,-1,-1); +} + +/** + * Cleanup all sensors + */ +void Sensor_Cleanup() +{ + for (int i = 0; i < g_num_sensors; ++i) { - g_sensors[i].id = i; - Data_Init(&(g_sensors[i].data_file)); + Sensor * s = g_sensors+i; + if (s->cleanup != NULL) + s->cleanup(s->user_id); } - - - - // Get the required ADCs - ADC_Export(ADC0); // Strain gauges x 4 - ADC_Export(ADC1); // Pressure sensor 1 - ADC_Export(ADC2); // Pressure sensor 2 - // ADC3 still unused (!?) - ADC_Export(ADC4); // Pressure regulator feedback(?) signal - ADC_Export(ADC5); // Microphone - - // Get GPIO pins //TODO: Confirm pins used with Electronics Team - GPIO_Export(GPIO0_30); // Mux A (strain 1) - GPIO_Set(GPIO0_30, false); - GPIO_Export(GPIO1_28); // Mux B (strain 2) - GPIO_Set(GPIO1_28, false); - GPIO_Export(GPIO0_31); // Mux C (strain 3) - GPIO_Set(GPIO0_31, false); - GPIO_Export(GPIO1_16); // Mux D (strain 4) - GPIO_Set(GPIO1_16, false); - - GPIO_Export(GPIO0_31); // Enclosure switch } /** @@ -93,7 +117,7 @@ void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg) char filename[BUFSIZ]; const char *experiment_name = (const char*) arg; - if (snprintf(filename, BUFSIZ, "%s_s%d", experiment_name, s->id) >= BUFSIZ) + if (snprintf(filename, BUFSIZ, "%s_%d", experiment_name, s->id) >= BUFSIZ) { Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ); } @@ -133,8 +157,6 @@ void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg) } Data_Close(&(s->data_file)); // Close DataFile - s->newest_data.time_stamp = 0; - s->newest_data.value = 0; Log(LOGDEBUG, "Stopped sensor %d", s->id); break; default: @@ -150,7 +172,7 @@ void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg) */ void Sensor_SetModeAll(ControlModes mode, void * arg) { - for (int i = 0; i < NUMSENSORS; i++) + for (int i = 0; i < g_num_sensors; i++) Sensor_SetMode(&g_sensors[i], mode, arg); } @@ -160,142 +182,21 @@ void Sensor_SetModeAll(ControlModes mode, void * arg) * @param sensor_id - The ID of the sensor * @param value - The value from the sensor to test */ -void Sensor_CheckData(SensorId id, double value) +void Sensor_CheckData(Sensor * s, double value) { - if( value > thresholds[id].max_error || value < thresholds[id].min_error) + 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", g_sensor_names[id],value, thresholds[id].max_error, thresholds[id].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 > thresholds[id].max_warn || value < thresholds[id].min_warn) + 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", g_sensor_names[id],value,thresholds[id].max_warn, thresholds[id].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); } } -/** - * Read a DataPoint from a Sensor; block until value is read - * @param id - The ID of the sensor - * @param d - DataPoint to set - * @returns True if the DataPoint was different from the most recently recorded. - */ -bool Sensor_Read(Sensor * s, DataPoint * d) -{ - - - - static bool result = true; - - //TODO: Remove this, code should be refactored to not use so many threads - // Although... if it works, it works... - static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - - pthread_mutex_lock(&mutex); //TODO: Reduce the critical section - - usleep(10); - - // Set time stamp - struct timeval t; - gettimeofday(&t, NULL); - d->time_stamp = TIMEVAL_DIFF(t, *Control_GetStartTime()); - - // Read value based on Sensor Id - int value; bool success = true; - //TODO: Can probably do this nicer than a switch (define a function pointer for each sensor) - // Can probably make the whole sensor thing a lot nicer with a linked list of sensors... - // (Then to add more sensors to the software, someone just writes an appropriate read function and calls Sensor_Add(...) at init) - // (I will do this. Don't do it before I get a chance, I don't trust you :P) - switch (s->id) - { - //TODO: Strain gauges should have their own critical section, rest of sensors probably don't need to be in a critical section - case STRAIN0: - success &= GPIO_Set(GPIO0_30, true); - success &= ADC_Read(ADC0, &value); - success &= GPIO_Set(GPIO0_30, false); - if (!success) - Fatal("Error reading strain gauge 0"); - break; - case STRAIN1: - success &= GPIO_Set(GPIO1_28, true); - success &= ADC_Read(ADC0, &value); - success &= GPIO_Set(GPIO1_28, false); - if (!success) - Fatal("Error reading strain gauge 1"); - break; - case STRAIN2: - success &= GPIO_Set(GPIO0_31, true); - success &= ADC_Read(ADC0, &value); - success &= GPIO_Set(GPIO0_31, false); - case STRAIN3: - success &= GPIO_Set(GPIO1_16, true); - success &= ADC_Read(ADC0, &value); - success &= GPIO_Set(GPIO1_16, false); - if (!success) - Fatal("Error reading strain gauge 2"); - break; - case PRESSURE0: - success &= ADC_Read(ADC1, &value); - break; - case PRESSURE1: - success &= ADC_Read(ADC5, &value); - break; - case PRESSURE_FEEDBACK: - success &= ADC_Read(ADC4, &value); - break; - case MICROPHONE: - success &= ADC_Read(ADC2, &value); - break; - case ENCLOSURE: - { - bool why_do_i_need_to_do_this = false; - success &= GPIO_Read(GPIO0_31, &why_do_i_need_to_do_this); - value = (int)why_do_i_need_to_do_this; - break; - } - case DILATOMETER: - { - // Will definitely cause issues included in the same critical section as ADC reads - // (since it will be the longest sensor to sample, everything else will have to keep waiting on it) - value = 0; - break; - } - - } - - d->value = (double)(value); //TODO: Calibration? Or do calibration in GUI - - pthread_mutex_unlock(&mutex); //TODO: Reduce the critical section - - - // Perform sanity check based on Sensor's ID and the DataPoint - Sensor_CheckData(s->id, d->value); - - // Update latest DataPoint if necessary - - if (result) - { - s->newest_data.time_stamp = d->time_stamp; - s->newest_data.value = d->value; - } - -#ifdef _BBB - //Not all cases have usleep, easiest here. - //TODO: May want to add a control option to adjust the sampling rate for each sensor? - // Also, we can get a more accurate sampling rate if instead of a fixed sleep, we calculate how long to sleep each time. - usleep(100000); -#endif - - /* - if (success) - Log(LOGDEBUG, "Successfully read sensor %d (for once)", s->id); - else - Log(LOGDEBUG, "Failed to read sensor %d (again)", s->id); - */ - return result && success; -} - /** * Record data from a single Sensor; to be run in a seperate thread * @param arg - Cast to Sensor* - Sensor that the thread will handle @@ -310,41 +211,43 @@ void * Sensor_Loop(void * arg) while (s->activated) { DataPoint d; - //Log(LOGDEBUG, "Sensor %d reads data [%f,%f]", s->id, d.time_stamp, d.value); - if (Sensor_Read(s, &d)) // If new DataPoint is read: + d.value = 0; + bool success = s->read(s->user_id, &(d.value)); + + struct timeval t; + gettimeofday(&t, NULL); + d.time_stamp = TIMEVAL_DIFF(t, *Control_GetStartTime()); + + if (success) { - //Log(LOGDEBUG, "Sensor %d saves data [%f,%f]", s->id, d.time_stamp, d.value); + + + Sensor_CheckData(s, d.value); Data_Save(&(s->data_file), &d, 1); // Record it } + else + Log(LOGWARN, "Failed to read sensor %s (%d,%d)", s->name, s->id,s->user_id); + + usleep(s->sample_us); } // Needed to keep pthreads happy - - Log(LOGDEBUG, "Sensor %d finished", s->id); + Log(LOGDEBUG, "Sensor %s (%d,%d) finished", s->name,s->id,s->user_id); return NULL; } /** - * Get a Sensor given an ID string - * @param id_str ID string - * @returns Sensor* identified by the string; NULL on error + * Get a Sensor given its name + * @returns Sensor with the given name, NULL if there isn't one */ -Sensor * Sensor_Identify(const char * id_str) -{ - char * end; - // Parse string as integer - int id = strtol(id_str, &end, 10); - if (*end != '\0') +Sensor * Sensor_Identify(const char * name) +{ + for (int i = 0; i < g_num_sensors; ++i) { - return NULL; + if (strcmp(g_sensors[i].name, name) == 0) + return &(g_sensors[i]); } - // Bounds check - if (id < 0 || id >= NUMSENSORS) - return NULL; - - - Log(LOGDEBUG, "Sensor \"%s\" identified", g_sensor_names[id]); - return g_sensors+id; + return NULL; } /** @@ -353,15 +256,16 @@ Sensor * Sensor_Identify(const char * id_str) * @param id - ID of sensor * @param format - Format */ -void Sensor_BeginResponse(FCGIContext * context, SensorId id, DataFormat format) +void Sensor_BeginResponse(FCGIContext * context, Sensor * s, DataFormat format) { // Begin response switch (format) { case JSON: FCGI_BeginJSON(context, STATUS_OK); - FCGI_JSONLong("id", id); - FCGI_JSONPair("name", g_sensor_names[id]); + FCGI_JSONLong("id", s->id); + FCGI_JSONLong("user_id", s->user_id); //NOTE: Might not want to expose this? + FCGI_JSONPair("name", s->name); break; default: FCGI_PrintRaw("Content-type: text/plain\r\n\r\n"); @@ -375,7 +279,7 @@ void Sensor_BeginResponse(FCGIContext * context, SensorId id, DataFormat format) * @param id - ID of the sensor * @param format - Format */ -void Sensor_EndResponse(FCGIContext * context, SensorId id, DataFormat format) +void Sensor_EndResponse(FCGIContext * context, Sensor * s, DataFormat format) { // End response switch (format) @@ -400,24 +304,30 @@ void Sensor_Handler(FCGIContext *context, char * params) double current_time = TIMEVAL_DIFF(now, *Control_GetStartTime()); int id = 0; + const char * name = ""; double start_time = 0; double end_time = current_time; const char * fmt_str; + double sample_s = 0; // key/value pairs FCGIValue values[] = { - {"id", &id, FCGI_REQUIRED(FCGI_INT_T)}, + {"id", &id, FCGI_INT_T}, + {"name", &name, FCGI_STRING_T}, {"format", &fmt_str, FCGI_STRING_T}, {"start_time", &start_time, FCGI_DOUBLE_T}, {"end_time", &end_time, FCGI_DOUBLE_T}, + {"sample_s", &sample_s, FCGI_DOUBLE_T} }; // enum to avoid the use of magic numbers typedef enum { ID, + NAME, FORMAT, START_TIME, END_TIME, + SAMPLE_S } SensorParams; // Fill values appropriately @@ -427,24 +337,68 @@ void Sensor_Handler(FCGIContext *context, char * params) return; } - // Error checking on sensor id - if (id < 0 || id >= NUMSENSORS) + Sensor * s = NULL; + if (FCGI_RECEIVED(values[NAME].flags)) + { + if (FCGI_RECEIVED(values[ID].flags)) + { + FCGI_RejectJSON(context, "Can't supply both sensor id and name"); + return; + } + s = Sensor_Identify(name); + if (s == NULL) + { + FCGI_RejectJSON(context, "Unknown sensor name"); + return; + } + } + else if (!FCGI_RECEIVED(values[ID].flags)) + { + FCGI_RejectJSON(context, "No sensor id or name supplied"); + return; + } + else if (id < 0 || id >= g_num_sensors) { FCGI_RejectJSON(context, "Invalid sensor id"); return; } - Sensor * s = g_sensors+id; + else + { + s = &(g_sensors[id]); + } + // Adjust sample rate if necessary + if (FCGI_RECEIVED(values[SAMPLE_S].flags)) + { + if (sample_s < 0) + { + FCGI_RejectJSON(context, "Negative sampling speed!"); + return; + } + s->sample_us = 1e6*sample_s; + } + + DataFormat format = Data_GetFormat(&(values[FORMAT])); // Begin response - Sensor_BeginResponse(context, id, format); + Sensor_BeginResponse(context, s, format); // Print Data Data_Handler(&(s->data_file), &(values[START_TIME]), &(values[END_TIME]), format, current_time); // Finish response - Sensor_EndResponse(context, id, format); + Sensor_EndResponse(context, s, format); + +} + +/** + * Get the Name of a Sensor + * @param id - ID number + */ +const char * Sensor_GetName(int id) +{ + return g_sensors[id].name; } diff --git a/server/sensor.h b/server/sensor.h index 32e1e2d..bb2fb9e 100644 --- a/server/sensor.h +++ b/server/sensor.h @@ -7,70 +7,81 @@ #define _SENSOR_H #include "data.h" +#include "device.h" +/** + * Maximum number of sensors program can be compiled with + * (If you get an error "Increase SENSORS_MAX from %d" this is what it refers to) + */ +#define SENSORS_MAX 10 +extern int g_num_sensors; // in sensor.c -/** Number of sensors **/ -#define NUMSENSORS 10 -/** Sensor ids - there should be correspondence with the names in g_sensor_names **/ -typedef enum SensorId +/** Structure to define the warning and error thresholds of the sensors **/ +//TODO: Replace with a call to an appropriate "Sanity" function? (see the actuator code) +typedef struct { - STRAIN0, - STRAIN1, - STRAIN2, - STRAIN3, - PRESSURE0, - PRESSURE1, - PRESSURE_FEEDBACK, - MICROPHONE, - ENCLOSURE, - DILATOMETER -} SensorId; - - - -/** Human readable names for the sensors **/ -extern const char * g_sensor_names[NUMSENSORS]; + /** Maximum safe value **/ + double max_error; + /** Minimum safe value **/ + double min_error; + /** Maximum value before a warning is reported **/ + double max_warn; + /** Minimum value before a warning is reported **/ + double min_warn; +} SensorThreshold; /** Structure to represent a sensor **/ typedef struct { /** ID number of the sensor **/ - SensorId id; + int id; + /** User defined ID number **/ + int user_id; /** DataFile to store sensor values in **/ DataFile data_file; /** Indicates whether the Sensor is active or not **/ bool activated; /** Thread the Sensor is running in **/ pthread_t thread; - /** Most recently recorded data **/ - DataPoint newest_data; - + /** Function to read the sensor **/ + ReadFn read; + /** Function to initialise the sensor **/ + InitFn init; + /** Function to cleanup the sensor **/ + CleanFn cleanup; + /** Human readable name of the sensor **/ + const char * name; + /** Thresholds on the sensor **/ + SensorThreshold thresholds; + /** Sampling rate **/ + int sample_us; + /** Averages per DataPoint **/ + int averages; + } Sensor; -// Structure to define the warning and error thresholds of the sensors -typedef struct -{ - double max_error; - double min_error; - double max_warn; - double min_warn; -} SensorThreshold; + extern void Sensor_Init(); // One off initialisation of *all* sensors +extern void Sensor_Cleanup(); // Cleanup all sensors extern void Sensor_SetModeAll(ControlModes mode, void * arg); extern void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg); extern void * Sensor_Loop(void * args); // Main loop for a thread that handles a Sensor extern bool Sensor_Read(Sensor * s, DataPoint * d); // Read a single DataPoint, indicating if it has changed since the last one -extern void Sensor_CheckData(SensorId id, double value); // Check a DataPoint -extern Sensor * Sensor_Identify(const char * str); // Identify a Sensor from a string Id +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 +extern const char * Sensor_GetName(int id); + + + #endif //_SENSOR_H diff --git a/server/sensors/Makefile b/server/sensors/Makefile new file mode 100644 index 0000000..b72201d --- /dev/null +++ b/server/sensors/Makefile @@ -0,0 +1,27 @@ +# Makefile for server software +CXX = gcc +FLAGS = -std=c99 -Wall -pedantic -g -I../ +#-I/usr/include/opencv -I/usr/include/opencv2/highgui For OpenCV +LIB = -lpthread +OBJ = strain.o resource.o piped.o +HEADERS = $(wildcard *.h) +RM = rm -f + +all.o : $(OBJ) + ar rvs sensors.a $(OBJ) + + +%.o : %.c + $(CXX) $(FLAGS) -c $< + +clean : + $(RM) $(BIN) + $(RM) *.o + +clean_full: #cleans up all backup files + $(RM) $(BIN) $(OBJ) + $(RM) *.*~ + $(RM) *~ + + + diff --git a/server/sensors/README b/server/sensors/README new file mode 100644 index 0000000..f267d5a --- /dev/null +++ b/server/sensors/README @@ -0,0 +1,20 @@ +To add a new type of sensor to the program: + +1. Create a .c and .h file +2. In the .h file, define an enum representing the id number of each sensor of that type (you can skip this if you only need a single sensor) +3. Implement a function for reading a sensor value; it should be of the form: bool Read(int id, double * value) + - id indicates which of the sensors of that type is being read (if you only have a single sensor you can ignore it) + - The function stores the result in the double pointed to by value + - It returns true on success and false on a (non fatal) error +4. Optionally add an initialisation function: bool Init(int id, char * name) +5. In ../sensor.c make a call to Sensor_Add in the Sensor_Init function, passing the appropriate arguments. + - They are: The name of the sensor (passed to Init, see 4.) + - The id number according to the enum (just pass 0 if you don't need more than one sensor) + - The Read function + - The Init function (or NULL if you don't need one) + - Threshold values in the order: + max_error, min_error, max_warn, min_warn (as documented in ../sensor.h) + - If your sensor doesn't have/need safety thresholds, just pass ridiculous values like 1e50 or 1e-50 + - Yeah, it's hacky, but it works. + - You may need to increase SENSORS_MAX in ../sensor.h if you go insane with sensor adding power +6. Add the .o file to Makefile (the OBJ variable) diff --git a/server/sensors/piped.c b/server/sensors/piped.c new file mode 100644 index 0000000..ae46cbe --- /dev/null +++ b/server/sensors/piped.c @@ -0,0 +1,121 @@ +/** + * @file piped.h + * @brief Sensor run by an external process and sent to this one through a pipe + * PURELY INCLUDED FOR TESTING PURPOSES + * This will work with any sensor that can unbuffer stdout + * ... So, although it's not recommended, you could write a sensor purely in something like python + * The FastCGI process will handle all the time stamps and stuff + */ + +#include "../log.h" +#include "../common.h" + +#include "piped.h" + +#include +#include +#include +#include + +#include // Defines DBL_MAX_10_EXP + + +typedef struct +{ + int pid; + int sv[2]; + FILE * stream; + +} Piped; + +static Piped g_piped[PIPED_MAX]; +static int g_num_piped = 0; + +bool Piped_Init(const char * name, int id) +{ + if (++g_num_piped > PIPED_MAX) + { + Fatal("Too many sensors; Increase PIPED_MAX from %d in piped.h and recompile", PIPED_MAX); + } + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, g_piped[id].sv) != 0) + Fatal("socketpair failed - %s", strerror(errno)); + + g_piped[id].pid = fork(); + if (g_piped[id].pid == 0) + { + dup2(g_piped[id].sv[0], fileno(stdin)); + dup2(g_piped[id].sv[0], fileno(stdout)); + + if (access(name, X_OK) == 0) //Check we STILL have permissions to start the file + { + execl(name, name, (char*)(NULL)); ///Replace process with desired executable + //execv(executablePath,arguments); ///Replace process with desired executable + } + else + { + Fatal("Can't execute file %s", name); + } + Fatal("execl error - %s", strerror(errno)); + } + else + { + g_piped[id].stream = fdopen(g_piped[id].sv[1], "r"); + setbuf(g_piped[id].stream, NULL); + } + return true; + +} +/** + * This function looks evil, but I swear it's legit + * @param id - The Piped process to read from + * @param value - Stores the value read (if successful) + * @returns true on success, false on failure + */ +bool Piped_Read(int id, double * value) +{ + if (g_piped[id].stream == NULL) + return false; + + // So need a buffer size big enough to fit all doubles but not too much bigger + static char buf[DBL_MAX_10_EXP+1]; + + // Using BUFSIZ is a bad idea, since we want to read near the end of the file + + // Seek back from the end of the file + fseek(g_piped[id].stream, -DBL_MAX_10_EXP, SEEK_END); + int len = fread(buf, 1, DBL_MAX_10_EXP, g_piped[id].stream); // Fill the buffer, note the length count + int count = 0; + int i = len-1; + for (i = len-1; i >= 0; --i) // Search for the last non-empty line in the buffer + { + if (buf[i] == '\n') + { + if (count++ > 1) + { + ++i; + break; + } + else + buf[i] = '\0'; + } + } + // Now sscanf a double from the string + if (sscanf(buf+i, "%lf", value) != 1) + { + Log(LOGDEBUG, "Can't interpret %s as double", buf); + return false; + } + return true; +} + +bool Piped_Cleanup(int id) +{ + fclose(g_piped[id].stream); + if (kill(g_piped[id].pid, 15) != 0) + { + Log(LOGWARN, "Couldn't kill piped %d - %s", g_piped[id].pid, strerror(errno)); + return false; + } + return true; +} diff --git a/server/sensors/piped.h b/server/sensors/piped.h new file mode 100644 index 0000000..92348f3 --- /dev/null +++ b/server/sensors/piped.h @@ -0,0 +1,15 @@ +#ifndef _PIPED_H +#define _PIPED_H + +/** + * @file piped.h + * @brief Sensor run by an external process and sent to this one through a pipe + * ... This is here as an example so that if people really hate our program they can still use the FastCGI API but make an entirely seperate sensor program + */ + +#define PIPED_MAX 1 + +extern bool Piped_Init(const char * name, int id); +extern bool Piped_Read(int id, double * value); +extern bool Piped_Cleanup(int id); +#endif //_PIPED_H diff --git a/server/sensors/resource.c b/server/sensors/resource.c new file mode 100644 index 0000000..fa52562 --- /dev/null +++ b/server/sensors/resource.c @@ -0,0 +1,30 @@ +#include "resource.h" + +#include "../log.h" +#include "../common.h" + +#include +#include + +bool Resource_Read(int id, double * value) +{ + struct rusage usage; + int err = getrusage(RUSAGE_SELF, &usage); + if (err != 0) + { + Log(LOGWARN, "Couldn't get resource information - %s", strerror(errno)); + } + switch (id) + { + case RESOURCE_CPU_USER: + *value = TIMEVAL_TO_DOUBLE(usage.ru_utime); + break; + case RESOURCE_CPU_SYS: + *value = TIMEVAL_TO_DOUBLE(usage.ru_stime); + break; + default: + Log(LOGWARN, "Unknown id %d", id); + return false; + } + return true; +} diff --git a/server/sensors/resource.h b/server/sensors/resource.h new file mode 100644 index 0000000..f605385 --- /dev/null +++ b/server/sensors/resource.h @@ -0,0 +1,17 @@ +/** + * @file resource.h + * @brief Testing sensors; gets rescource usage + */ + +#include + +/** + * Enum of sensor ids + */ +typedef enum +{ + RESOURCE_CPU_USER, + RESOURCE_CPU_SYS +} ResourceID; + +extern bool Resource_Read(int id, double * value); diff --git a/server/sensors/strain.c b/server/sensors/strain.c new file mode 100644 index 0000000..3ee1624 --- /dev/null +++ b/server/sensors/strain.c @@ -0,0 +1,102 @@ +#include "strain.h" + +#include "../log.h" +#include "../bbb_pin.h" + +#include + +#define STRAIN_ADC ADC0 + +/** + * Convert Strain gauge id number to a GPIO pin on the Mux + * @param id - The strain gauge id + * @returns - GPIO pin number + */ +static int Strain_To_GPIO(StrainID id) +{ + // Could use a lookup table; that would assume strict ordering of the enum in strain.h + //static int lookup[] = {GPIO0_30, GPIO1_28,GPIO0_31,GPIO1_16}; + //if (id < STRAIN0 || id > STRAIN3) + // Fatal("unknown strain id %d", id); + //return lookup[id]; + + switch (id) + { + case STRAIN0: + return GPIO0_30; + case STRAIN1: + return GPIO1_28; + case STRAIN2: + return GPIO0_31; + case STRAIN3: + return GPIO1_16; + default: + Fatal("Unknown StrainID %d", id); + return -1; // Should never happen + } +} + +/** + * Convert ADC Strain reading to a physically meaningful value + * @param reading - The ADC reading + * @returns Something more useful + */ +static double Strain_Calibrated(int reading) +{ + return (double)(reading); +} + +/** + * Initialise a Strain gauge + * @param id - The strain gauge to initialise + * @param name - Name of the strain gauge; ignored + * @returns true if initialisation was successful + */ +bool Strain_Init(const char * name, int id) +{ + int gpio_num = Strain_To_GPIO(id); + GPIO_Export(gpio_num); + if (!GPIO_Set(gpio_num, false)) + Fatal("Couldn't set GPIO%d for strain sensor %d to LOW", gpio_num, id); + + static bool init_adc = false; + if (!init_adc) + { + init_adc = true; + ADC_Export(STRAIN_ADC); + } + return true; +} + +/** + * Read from a Strain gauge + * @param id - The strain gauge to read + * @param val - Will store the read value if successful + * @returns true on successful read + */ +bool Strain_Read(int id, double * value) +{ + static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // CRITICAL SECTION - Only one Strain gauge can be read at a time + pthread_mutex_lock(&mutex); + + int gpio_num = Strain_To_GPIO(id); + + // Set the multiplexer + if (!GPIO_Set(gpio_num, true)) + Fatal("Couldn't set GPIO%d for strain sensor %d to HIGH (before reading)", gpio_num,id); + + // Read the ADC + int tmp = 0; + bool result = ADC_Read(STRAIN_ADC, &tmp); // If this fails, it's not fatal + + //TODO: Callibrate? + *value = Strain_Calibrated(tmp); + + // Unset the multiplexer + if (!GPIO_Set(gpio_num, false)) + Fatal("Couldn't set GPIO%d for strain sensor %d to LOW (after reading)", gpio_num, id); + + pthread_mutex_unlock(&mutex); + + return result; +} diff --git a/server/sensors/strain.h b/server/sensors/strain.h new file mode 100644 index 0000000..1afb625 --- /dev/null +++ b/server/sensors/strain.h @@ -0,0 +1,22 @@ +#ifndef _STRAIN_H +#define _STRAIN_H + +#include + +/** + * Enum of strain IDs + */ +typedef enum +{ + STRAIN0, + STRAIN1, + STRAIN2, + STRAIN3 +} StrainID; + +// Initialise a strain gauge +extern bool Strain_Init(const char * name, int id); +// Read from a strain gauge +extern bool Strain_Read(int id, double * value); + +#endif //_STRAIN_H diff --git a/server/valgrind.sh b/server/valgrind.sh index fc5c8d3..4ebea4a 100755 --- a/server/valgrind.sh +++ b/server/valgrind.sh @@ -1,3 +1,4 @@ #!/bin/bash . parameters -valgrind ./server $parameters +valgrind --leak-check=full --track-origins=yes ./server $parameters +#valgrind ./server $parameters diff --git a/testing/count.py b/testing/count.py new file mode 100755 index 0000000..cbbc501 --- /dev/null +++ b/testing/count.py @@ -0,0 +1,14 @@ +#!/usr/bin/python -u + +import sys +import os +import time +import datetime + +start = datetime.datetime.now() +while True: + timed = map(float, str(datetime.datetime.now() - start).split(":")) + count = timed[0]*60.0*60.0+timed[1]*60.0+timed[2] + print(str(count)) + +