Monday, November 7, 2011
Apt-get in the iphone
Highly annoying to find this. With a jail broken iPhone you need to install the package "AptBackup" to get command line apt-get.
Labels:
iPhone,
system admin,
webserver
chomping with boost
Always convenient
#include <boost/algorithm/string.hpp> ... boost::algorithm::trim_if(str,boost::algorithm::is_any_of(" \t\n"));
Friday, November 4, 2011
Recursive Templates -- Iterable poly dimensional associative arrays
And here is the template based array with a iterator added to it that is capable of iterating all the final cells in the array. Its not the most efficient but it works.
Also note the operator<< is a bit busted at the moment.. Will fix it when im not about to fall asleep.
//built with mimggw g++ (gcc version 4.5.0 (GCC)) #include <iostream> #include <map> #include <string> #include <ostream> //ploy dimensional iterators // what we are dealing with here is a N dimensional deep map template <int n, typename Key, typename Value > class MultiDimMap { public: //problem here for types u cant reach inside of the yet to be defined struct and get its type. You need to have it pre-definable. typedef MultiDimMap<n-1, Key, Value> InnerMapClass; typedef std::map<Key, InnerMapClass> MapClass; //instance depend types. //"typename" is need here so the compiler knows we are pointing //to a type that is not yet a solid implementation. typedef typename InnerMapClass::iterator InnerMapClassItr; typedef typename MapClass::iterator MapClassItr; class iterator { public: Value& operator*() { return *inner; } iterator& operator++() { ++inner; valid_or_next(); return *this; } bool operator!=(const iterator& oit) const { return (oit.outer != outer && (map->end() == outer || oit.map->end() == oit.outer || oit.inner != inner)); } bool operator==(const iterator& oit) const { return !(oit != *this); } std::ostream& print(std::ostream& out) { out << outer->first << ":"; inner.print(out); return out; } iterator() : map(NULL), outer(), inner() {} iterator(MapClass& map_, const MapClassItr& outer_) : map(&map_), outer(outer_), inner() { inner = outer->second.begin(); valid_or_next(); } private: void valid_or_next() { while( (map->end() != outer) && (outer->second.end() == inner) ) { ++outer; inner = outer->second.begin(); } } MapClass* map; MapClassItr outer; InnerMapClassItr inner; }; iterator begin() { return iterator(map, map.begin()); } iterator end() { return iterator(map, map.end() ); } InnerMapClass& operator[](Key key) { return map[key]; } private: MapClass map; }; //The recursive termination.. template <typename Key, typename Value > class MultiDimMap<1, Key, Value > { public: typedef std::map<Key, Value> MapClass; //instance depend types. //"typename" is need here so the compiler knows we are pointing //to a type that is not yet a solid implementation. typedef typename MapClass::iterator MapClassItr; class iterator { public: Value& operator*() { return it->second; } iterator& operator++() { it++; return *this; } bool operator!=(const iterator& oit) const { return oit.it != it; } bool operator==(const iterator& oit) const { return !(oit != *this); } std::ostream& print(std::ostream& out) const { out << it->first << ":" << it->second; return out; } iterator() : it() {} iterator(const MapClassItr& it_) : it(it_) {} private: MapClassItr it; }; iterator begin() { return iterator(map.begin()); } iterator end() { return iterator(map.end() ); } Value& operator[](Key key) { return map[key]; } private: MapClass map; }; //this hates me fix it later //template <int n, typename Key, typename Value > //std::ostream& operator<<( std::ostream& out, const typename MultiDimMap<n, Key, Value>::iterator& it ) //{ // return it.print(out); //} int main() { MultiDimMap<1, std::string, int> stringInt1DMap; stringInt1DMap["a"] = 2; stringInt1DMap["b"] = 5; MultiDimMap<2, std::string, int> stringInt2DMap; stringInt2DMap["d"]["b"] = 5; stringInt2DMap["a"]["c"] = 2; stringInt2DMap["d"]["c"] = 3; MultiDimMap<3, int, std::string> intString3DMap; intString3DMap[2][1][1] = "first"; intString3DMap[1][1][2] = "second"; intString3DMap[2][2][1] = "third"; std::cout << "1d Iteration:" << std::endl; MultiDimMap<1, std::string, int>::iterator it1d; for(it1d = stringInt1DMap.begin(); it1d != stringInt1DMap.end(); ++it1d) { std::cout << " "; it1d.print(std::cout); std::cout << " Value:" << *it1d << std::endl; } std::cout << "2d Iteration:" << std::endl; MultiDimMap<2, std::string, int>::iterator it2d; for(it2d = stringInt2DMap.begin(); it2d != stringInt2DMap.end(); ++it2d) { std::cout << " "; it2d.print(std::cout); std::cout << " Value:" << *it2d << std::endl; } std::cout << "3d Iteration:" << std::endl; MultiDimMap<3, int, std::string>::iterator it3d; for(it3d = intString3DMap.begin(); it3d != intString3DMap.end(); ++it3d) { std::cout << " "; it3d.print(std::cout); std::cout << " Value:" << *it3d << std::endl; } }
Thursday, November 3, 2011
Recursive Templates -- poly dimensional associative arrays
Another step to the poly dimensional iterators are the generic ploy dimensional arrays. These come in handy. The Trick to realize is that the types need to be predefined first so 2 typedefs are needed.
//built with mimggw g++ (gcc version 4.5.0 (GCC)) #include <iostream> #include <map> #include <string> //ploy dimensional associative arrays template <int n, typename Key, typename Value > struct MultiDimMap { //problem here for types u cant reach inside of the yet to be defined struct and get its type. You need to have it predefinable. typedef MultiDimMap<n-1, Key, Value> InnerMapClass; typedef std::map<Key, InnerMapClass> MapClass; InnerMapClass& operator[](Key key) { return map[key]; } private: MapClass map; }; template <typename Key, typename Value > struct MultiDimMap<1, Key, Value > { typedef std::map<Key, Value> MapClass; Value& operator[](Key key) { return map[key]; } private: MapClass map; }; int main() { MultiDimMap<1, std::string, int> stringInt1DMap; stringInt1DMap["a"] = 2; MultiDimMap<2, std::string, int> stringInt2DMap; stringInt2DMap["a"]["b"] = 2; stringInt2DMap["a"]["c"] = 3; MultiDimMap<3, int, std::string> intString3DMap; intString3DMap[1][1][1] = "first"; intString3DMap[1][1][2] = "second"; intString3DMap[2][2][1] = "third"; std::cout << intString3DMap[2][2][1] << std::endl; std::cout << stringInt2DMap["a"]["c"] << std::endl; }
Wednesday, November 2, 2011
Recursive Templates
I have been thinking about doing a generic N dimension container and iterator template recently and started putting together some of the code for it. In order to make it work a good understanding of Template recursion is needed. Here is an trivial example
//built with mimggw g++ (gcc version 4.5.0 (GCC)) #include <iostream> template <int n> struct Factorial { enum { RET = Factorial<n-1>::RET * n }; }; template <> struct Factorial<0> { enum { RET = 1 }; }; int main() { std::cout << Factorial<1>::RET << std::endl; std::cout << Factorial<2>::RET << std::endl; std::cout << Factorial<3>::RET << std::endl; std::cout << Factorial<4>::RET << std::endl; }
Thursday, October 27, 2011
Homemade Geiger Counter - Part 3 -- The Arduino software
WARNING >>>>>>LONG>>>>>> POST
The core processor in the Geiger counter is a Arduino ATmega328. It has 2kBytes of sdram 32kB of Flash and 2kb of EEPROM. And its running at 16Khz. Consider this carefully.
It means that memory will be completely filled with the following;
* 2000 character string
* 400 word sentence (at a 5 char average per word )
* an array of 500 32 bit integers
It means that excessive overhead and code niceties are often sacrificed for the sake of code speed.
Since it is a waste of more memory to keep a table of malloced and freed areas of memory embedded software is generally designed to using compile time data structures or stack allocated variables rather than heap data.
The Geigers counters code is built in this manor, Its not petty but its functional and efficient. The code is divided into 2 files the header and main code file
The header contains the constants such as which parts of that hardware and wired to what pins.
If you look closely you might not that I made a mistake when i selected the pins for the LCD since A3(Analogue 3) is already in use by the WIZNET(wireless radio) chip.
Another point to note is that is the "state" and "LcdDisplayMode" enums, this clearly implies that the code makes use of at least 1 state machine. In actual fact there are several but more on that later. I often consider stateful code to be the poor-mans threads. Coding this way allows the low spec hardware to quickly juggle a rather large set of real-time tasks without taking too much time for each. All you have to do a a code is segment the code into manageable chunks and allocate a state for each subsection or task.
You will note the "tubes" variable uses the array of structs trick so that I can statically allocate a mix of strings and integers into its initialization. This array provides the factory standard calibration constants for Caesium and the most commonly found Geiger tubes that are compatible with the NetRad shield, as you can see the board was built to be very flexible.
Also note that Arduinos PROGMEM macro keeps this data firmly out of SDRAM and EEPROM memory and in the flash. This makes it necessary to later load the data from the flash when needed but saves the SDRAM from instant overflow and frees up more working memory. I also encountered a problem with putting floats into the flash so dividing the mantissa and exponent become necessary.
And the definitions of the main memory stores in the system state_t and settings_t.
* state_t is the non-permanent set of operating conditions, it will exist in SDRAM
* settings_t is the permanent data that is loaded and save to the EEPROM. In theory I can make this consume up to 2k however it needs to be loaded into SDRAM when the device is operating, as a result you still need to use it sparingly, or break it into pages.
The Arduino comes with a nice set of headers, the internal unit support is provided by the avr libs. While the LiquidCrystal, Ethernet support the most common of peripheral devices used with by hobbyists.
As you can see the take the static compile time object approach common in embedded software. One note here new and delete don't work in the Arduino compilers so don't even bother trying to use them.
One large draw back with the Ardiunos is that printf is missing and the output interfaces tend to be inhomogeneous enough to be frustrating. In the end it annoyed me sufficiently well enough that I ended up writing a facade for the whole mess. It of course adds a slight overhead but in terms debug; the ability to redirect any output to network, serial port or lcd was invaluable.
The main user interface to the device is the USB based serial port, this interface is how the device is initially configured and calibrated(and its software is uploaded via this also). The next part of the code will handle user input via this method.
The core routine in this was the "cmdPoll" this would simply gather input coming in from the serial port until a enter is struck. The gathered line is then dispatched for processing in the "cmdParse" function, which compares it to another array of strings and function pointers. This then dispatch's it out to the found command function or dumps the help if needed.
Note that the majority of the commands are build as getter/setters. If ran parameter-less they will dump the current setting. But if a parameter is supplied it will first update it and then dump the new setting. It reduces coding effort and cuts down the mess of commands.
The Geiger is not always connected to a serial port. User input can also come in from the pull up switch added on the LCD shield and wired to the A1 pin. The button at the moment is setup to just toggle the LCD display modes. As you can see the lcd is refreshed in proxy from the button push via lcd_refresh this is so that other areas of the code can also trigger the lcd to change.
Additionally for accutate computations of the radiation readings special care needs to be taken to compute the amount of time that has elapased between readings and updates of the clock. Poorly written clocking code will often drift quite dramatically, this destroying your readings accuracy. The key line is the "state.now = millis();" which samples and records the time atomically as possible, The calculations then account for timer overflows and computes the full time delta using the current and prior samples of the clock.
The final interface on the device is the Ethernet port. Using the port is a bit complex but luckly the Arduino has a Ethernet lib complete with a Web client. As a result all I really need to do is generate a raw HTTP get request to a waiting server with the current raw reading for it to save. And at some point later read back its response.
This bring us to the main body of the code. The Arduino's dont have a main per-say. They use 2 functions. The "setup" function is a run once piece of code just after reset. The "loop" function is basically an endless while(1) loop. The Chibi board is setup so that Ardiuno uses "wdt_enable(WDTO_8S)" as a hardware stop watch. If the stop watch runs out the hardware resets and starts fresh(In this case its was set to 8 secs). The later "wdt_reset()" restarts the timer freash for the next iteration of the loop. This way the Geiger can never get stuck in an unexpected state.
Also note the only interrupt that is registered in the system is the onPulse function. This is the most critical piece of the code, it counts the output from the Geiger tube. Note that its not protected from multiple interrupts, this is because the geiger tubes dead time is sufficently long enough(and the code is next to atomic anyway) for the code to complete and be ready for the next event.
The main loop can be summarized as
* handle reset
* handle serial cmds
* handle user feedback of radiative events
* compute new total uptime
* compute new reading if sampling period is over
* handle button input LCD output
* handle network setup
* handle web client communication
The core processor in the Geiger counter is a Arduino ATmega328. It has 2kBytes of sdram 32kB of Flash and 2kb of EEPROM. And its running at 16Khz. Consider this carefully.
It means that memory will be completely filled with the following;
* 2000 character string
* 400 word sentence (at a 5 char average per word )
* an array of 500 32 bit integers
It means that excessive overhead and code niceties are often sacrificed for the sake of code speed.
Since it is a waste of more memory to keep a table of malloced and freed areas of memory embedded software is generally designed to using compile time data structures or stack allocated variables rather than heap data.
The Geigers counters code is built in this manor, Its not petty but its functional and efficient. The code is divided into 2 files the header and main code file
The header contains the constants such as which parts of that hardware and wired to what pins.
If you look closely you might not that I made a mistake when i selected the pins for the LCD since A3(Analogue 3) is already in use by the WIZNET(wireless radio) chip.
Another point to note is that is the "state" and "LcdDisplayMode" enums, this clearly implies that the code makes use of at least 1 state machine. In actual fact there are several but more on that later. I often consider stateful code to be the poor-mans threads. Coding this way allows the low spec hardware to quickly juggle a rather large set of real-time tasks without taking too much time for each. All you have to do a a code is segment the code into manageable chunks and allocate a state for each subsection or task.
enum states { RESET = 0, SOFT_RESTART, INIT_DHCP, OBTAIN_IP, INIT_CLIENT, NORMAL, CONNECT, SEND_DATA, RESP_DATA }; typedef enum { LCD_AVE_READING, LCD_INST_READING, LCD_STATE, LCD_UPTIME, LCD_MAX_MODE } LcdDisplayMode; // DEVICE SETTINGS -- REPLACE WITH A PROPER MAC ADDRESS byte macAddress[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xBA, 0xBE }; // DEVICE SETTINGS NetRAD - this is specific to the NetRAD board #define GEIGER_INTERRUPT_PIN 1 // pin of the gieger counter #define PIN_SPKR 6 // pin number of piezo speaker #define PIN_LED 7 // pin number of event LED #define WIZNET_RESET_PIN A1 #define RADIO_SELECT A3 //DEVICE SETTINGS LCD Shield #define BUTTON A0 #define LCD_PINS 9,8,A2,A5,A3,A4 #define URI_MAX 20 #define LAN_ON_MASK 0x80 #define DHCP_ON_MASK 0x40 #define FAIL_COUNT_MASK 0x3f
You will note the "tubes" variable uses the array of structs trick so that I can statically allocate a mix of strings and integers into its initialization. This array provides the factory standard calibration constants for Caesium and the most commonly found Geiger tubes that are compatible with the NetRad shield, as you can see the board was built to be very flexible.
Also note that Arduinos PROGMEM macro keeps this data firmly out of SDRAM and EEPROM memory and in the flash. This makes it necessary to later load the data from the flash when needed but saves the SDRAM from instant overflow and frees up more working memory. I also encountered a problem with putting floats into the flash so dividing the mantissa and exponent become necessary.
#define MAX_TUBE 8 #define CUSTOM_TUBE 1 typedef struct { const char* label; uint32_t convFactorMag; //something breaks if u put floats into PROGMEM.. int8_t convFactorExp; } TubeEntry; // this is ugly but.. dont blame me http://arduino.cc/en/Reference/PROGMEM prog_char tubeLabel1[] PROGMEM = "UNKNOWN"; prog_char tubeLabel2[] PROGMEM = "CUSTOM_TUBE"; prog_char tubeLabel3[] PROGMEM = "LND_712"; prog_char tubeLabel4[] PROGMEM = "SBM_20"; prog_char tubeLabel5[] PROGMEM = "J408GAMMA"; prog_char tubeLabel6[] PROGMEM = "J306BETA"; prog_char tubeLabel7[] PROGMEM = "INSPECTOR"; prog_char tubeLabel8[] PROGMEM = "CRM100"; //floats dont seem to be healthy in PROGMEM either! // 1CPM = ?? uS/hr PROGMEM TubeEntry tubes[MAX_TUBE] = { // label, uSv/hr factor { tubeLabel1, 0, 0 }, // ??? - use nothing! { tubeLabel2, 0, 0 }, // ??? - use conversion factor without caring { tubeLabel3, 2333, -6 }, // 0.002333 - http://www.lndinc.com/products/348/ { tubeLabel4, 57, -4 }, // 0.0057 - http://www.libelium.com/wireless_sensor_networks_to_control_radiation_levels_geiger_counters { tubeLabel5, 0, 0 }, // ??? - North Optic { tubeLabel6, 0, 0 }, // ??? - North Optic { tubeLabel7, 29, -4 }, // 0.0029 - { tubeLabel8, 0, 0 }, // ??? - };
And the definitions of the main memory stores in the system state_t and settings_t.
* state_t is the non-permanent set of operating conditions, it will exist in SDRAM
* settings_t is the permanent data that is loaded and save to the EEPROM. In theory I can make this consume up to 2k however it needs to be loaded into SDRAM when the device is operating, as a result you still need to use it sparingly, or break it into pages.
//static flashed settings typedef struct { //tube/counter settings float conversionCoefficient; // The conversion coefficient from cpm to µSv/h uint32_t readingIntervalMillis; uint8_t tubeModel; uint8_t emaHalfLife; // the number of samples before 50% redution for Expondential average comptation //ether setings uint8_t lanDhcpFails; //bit 1 lanOn, bit 2dhcpOn, bit 3-8 FailedConsCount uint8_t ipAddr[4]; uint8_t gateway[4]; uint8_t subnet[4]; //upload server settings uint8_t uploadIPAddr[4]; char uploadURI[URI_MAX]; } settings_t; // Interrupt mode: // * For most geiger counter modules: FALLING // * Geiger Counter Twig by Seeed Studio: RISING //dynamic runtime settings typedef struct { //execution state uint8_t state; uint8_t lcd_mode; bool lcd_refresh; //lan status uint8_t connFailCnt; uint32_t dhcpMaintainceTime; // The last connection time to disconnect from the server after uploaded feeds uint32_t lastConnectionTime; // The last connection time to disconnect from the server after uploaded feeds //time status uint32_t now; // millisec accumulative counter -- prone to overflow! // uptime status.. uint16_t upTimeMillis; uint8_t upTimeSec; uint8_t upTimeMin; uint8_t upTimeHours; uint8_t upTimeDays; // reading data last 10.. volatile uint8_t eventFlag; // Event flag signals when a geiger event has occurred uint32_t count; // Value to store counts per minute //current event uint32_t lastReading; // millisec accumative counter -- prone to overflow! uint32_t totalCount; float countsPerMinute; float microsievertPerHour; //Exponential moving average float emaCountsPerMinute; float emaMicrosievertPerHour; } state_t;
The Arduino comes with a nice set of headers, the internal unit support is provided by the avr libs. While the LiquidCrystal, Ethernet support the most common of peripheral devices used with by hobbyists.
As you can see the take the static compile time object approach common in embedded software. One note here new and delete don't work in the Arduino compilers so don't even bother trying to use them.
#include <stdint.h> #include <avr/eeprom.h> #include <avr/wdt.h> #include <limits.h> #include <LiquidCrystal.h> #include <SPI.h> #include <Ethernet.h> #include "PrivateSettings.h" // this holds the info for the device static settings_t settings; // holds the control info for the device static state_t state; //Web Client Client client; //LCD LiquidCrystal lcd(LCD_PINS);
One large draw back with the Ardiunos is that printf is missing and the output interfaces tend to be inhomogeneous enough to be frustrating. In the end it annoyed me sufficiently well enough that I ended up writing a facade for the whole mess. It of course adds a slight overhead but in terms debug; the ability to redirect any output to network, serial port or lcd was invaluable.
//Print wrapper system.. cause it sucks without it class PrintCore { public: virtual void endl() const = 0; virtual void clr() const = 0; virtual void p(const unsigned long val) const = 0; virtual void p(const unsigned int val) const = 0; virtual void p(const int val) const = 0; virtual void p(const char* val) const = 0; virtual void p(const char val) const = 0; }; class PrintLCD : public PrintCore { private: mutable int line; public: PrintLCD() : line(0) {} virtual void endl() const { line = (line+1)%2; lcd.setCursor(0,line); } virtual void clr() const { lcd.clear(); line = 0; lcd.setCursor(0,0); } virtual void p(const unsigned long val) const { lcd.print(val,DEC); } virtual void p(const unsigned int val) const { lcd.print(val); } virtual void p(const int val) const { lcd.print(val); } virtual void p(const char* val) const { lcd.print(val); } virtual void p(const char val) const { lcd.print(val); } }; class PrintSerial : public PrintCore { virtual void endl() const { Serial.println(); } virtual void clr() const { } virtual void p(const unsigned long val) const { Serial.print(val,DEC); } virtual void p(const unsigned int val) const { Serial.print(val); } virtual void p(const int val) const { Serial.print(val); } virtual void p(const char* val) const { Serial.print(val); } virtual void p(const char val) const { Serial.print(val); } }; class PrintWebClient : public PrintCore { virtual void endl() const { client.println(); } virtual void clr() const { } virtual void p(const unsigned long val) const { client.print(val,DEC); } virtual void p(const unsigned int val) const { client.print(val); } virtual void p(const int val) const { client.print(val); } virtual void p(const char* val) const { client.print(val); } virtual void p(const char val) const { client.print(val); } }; //main print wrapper.. class PrintClass : public PrintCore { PrintCore* core; PrintLCD lcd_mode; PrintSerial serial_mode; PrintWebClient client_mode; public: PrintClass() { serial(); } //mode void serial() { core = &serial_mode; } void lcd() { core = &lcd_mode; } void client() { core = &client_mode; } //standard virtual void endl() const { core->endl(); } virtual void clr() const { core->clr();} virtual void p(const unsigned long val) const { core->p(val); } virtual void p(const unsigned int val) const { core->p(val); } virtual void p(const int val) const { core->p(val); } virtual void p(const char* val) const { core->p(val); } virtual void p(const char val) const { core->p(val); } //commons void pln(const char* val) const { core->p(val); core->endl(); } void ulng(const unsigned long val) const { core->p(val); } //specials void prog(const prog_char* data) const { //IN FLASH(code) print //WARNING ardindo have to use special functions conserve sram use // this wraps up the print string to use the PSTR flash offload string char c=pgm_read_byte(data++); while(c != 0) { p(c); c=pgm_read_byte(data++); } } void progln(const prog_char* data) const { prog(data); endl(); } void pad(const int val, int padding=-1) const { int mult = 1; if (padding > 0) { //not designed for -ves while(--padding > 0) mult *= 10; while((val < mult) && (mult != 1)) { p('0'); mult /= 10; } } p((int)val); } void flt(double val, byte precision) const { if( precision <= 0) return; // prints val with number of decimal places determine by precision // precision is a number from 0 to 6 indicating the desired decimial places // example: printDouble( 3.1415, 2); // prints 3.14 (two decimal places) //rounding unsigned long mult = 1; //start at 10 for rounding byte padding = precision-1; while(precision--) mult *=10; val += 0.5/mult; //round p(int(val)); //prints the int part p('.'); // print the decimal point unsigned long frac; //get fraction and remove negative if(val >= 0) frac = (val - int(val)) * mult; else frac = (int(val)- val ) * mult; //padding compute unsigned long frac1 = frac; while(frac1 /= 10) padding--; //print padding while(padding--) p('0'); //print faction //p(frac,DEC) ; p(frac) ; } //OK getting highly geiger specific now... // Just a utility function to nicely format an IP address. void ip(const uint8_t* ipAddr) { for(int i = 0;i < 4; i++) { if (i!=0) p('.'); p((int)ipAddr[i]); } } void upTime(bool lng=true) { const char* comma = PSTR(":"); pad(state.upTimeDays); prog(PSTR(" days ")); if(!lng) endl(); pad(state.upTimeHours , 2); prog(comma); pad(state.upTimeMin , 2); prog(comma); pad(state.upTimeSec , 2); prog(comma); if(lng) pad(state.upTimeMillis, 3); } void conversionFactor() { prog(PSTR("ConversionFactor : 1CPM is ")); flt(settings.conversionCoefficient,4); prog(PSTR(" uS/Hr")); if (settings.conversionCoefficient != 0) { prog(PSTR("Hence 1 uS/h is : ")); flt((1.0/settings.conversionCoefficient),4); prog(PSTR(" CPM ")); } } void tubeLabel() { if (settings.tubeModel < MAX_TUBE) { prog(PSTR(" - ")); const char* labelProgmemPtr = (const char*)pgm_read_word(&tubes[settings.tubeModel].label); prog(labelProgmemPtr); } } }; PrintClass print;
The main user interface to the device is the USB based serial port, this interface is how the device is initially configured and calibrated(and its software is uploaded via this also). The next part of the code will handle user input via this method.
The core routine in this was the "cmdPoll" this would simply gather input coming in from the serial port until a enter is struck. The gathered line is then dispatched for processing in the "cmdParse" function, which compares it to another array of strings and function pointers. This then dispatch's it out to the found command function or dumps the help if needed.
Note that the majority of the commands are build as getter/setters. If ran parameter-less they will dump the current setting. But if a parameter is supplied it will first update it and then dump the new setting. It reduces coding effort and cuts down the mess of commands.
/**************************************************************************/ // Set address /**************************************************************************/ void cmdSetLanOn(char *args) { if (args) { if (strtol(args, NULL, 10) != 0) settings.lanDhcpFails |= LAN_ON_MASK; else settings.lanDhcpFails &= ~LAN_ON_MASK; state.state = SOFT_RESTART; } print.prog(PSTR("Lan is set to ")); print.pad((settings.lanDhcpFails & LAN_ON_MASK) != 0); print.progln(PSTR(" Off=0")); } void cmdSetDhcpOn(char *args) { if (args) { if (strtol(args, NULL, 10) != 0) settings.lanDhcpFails |= DHCP_ON_MASK; else settings.lanDhcpFails &= ~DHCP_ON_MASK; state.state = SOFT_RESTART; } print.prog(PSTR("DHCP set to ")); print.pad((settings.lanDhcpFails & DHCP_ON_MASK) != 0); print.progln(PSTR(" Off=0")); } void cmdSetFailsUntilReset(char *args) { if (args) { uint8_t fails = strtol(args, NULL, 10); fails = fails & FAIL_COUNT_MASK; settings.lanDhcpFails = (settings.lanDhcpFails & ~FAIL_COUNT_MASK) | fails; state.state = SOFT_RESTART; } print.prog(PSTR("Max Fails til reset set to ")); print.pad(settings.lanDhcpFails & FAIL_COUNT_MASK); } void readIP(char *args, uint8_t* ipAddr) { if (args) { char* part = strtok(args, "."); for(int i = 0;(i < 4) && (part != NULL); i++) { ipAddr[i] = strtol(part, NULL, 10); part = strtok(NULL, "."); } state.state = SOFT_RESTART; } } void cmdSetIpAddr(char *args) { readIP(args, settings.ipAddr); print.prog(PSTR("IP set to: ")); print.ip(settings.ipAddr); } void cmdSetGateway(char *args) { readIP(args, settings.gateway); print.prog(PSTR("Gateway set to: ")); print.ip(settings.gateway); } void cmdSetSubnet(char *args) { readIP(args, settings.subnet); print.prog(PSTR("Subnet set to: ")); print.ip(settings.subnet); } void cmdSetUploadIpAddr(char *args) { readIP(args, settings.uploadIPAddr); print.prog(PSTR("Upload IP address set to: ")); print.ip(settings.uploadIPAddr); } void cmdSetUploadURI(char *args) { if (args) { uint32_t len = strlen(args); if (len > (URI_MAX-1)) len = (URI_MAX-1); state.state = SOFT_RESTART; memset(settings.uploadURI, 0 , URI_MAX); memcpy(settings.uploadURI, args, strlen(args)); } print.prog(PSTR("Upload URI set to: ")); Serial.println(settings.uploadURI); } /**************************************************************************/ // TUBE Settings /**************************************************************************/ void cmdSetTube(char *args) { if (args) { settings.tubeModel = (uint32_t)strtol(args, NULL, 10); if (settings.tubeModel > MAX_TUBE) settings.tubeModel = 0; if (settings.tubeModel != CUSTOM_TUBE) { //ugly -- but something goes wrong with floats in PROGMEM... uint32_t convFactMag = (pgm_read_word(&tubes[settings.tubeModel].convFactorMag)); int8_t convFactExp = (pgm_read_byte(&tubes[settings.tubeModel].convFactorExp)); float convFactor = convFactMag; if (convFactExp < 0) while (convFactExp != 0) { convFactor /= 10.0; convFactExp++; } else if (convFactExp > 0) while (convFactExp != 0) { convFactor *= 10.0; convFactExp--; } settings.conversionCoefficient = convFactor; } } print.prog(PSTR("Tube model: ")); print.pad(settings.tubeModel); print.tubeLabel(); print.endl(); print.conversionFactor(); } void cmdSetConversionFactor(char *args) { if (args) settings.conversionCoefficient = strtol(args, NULL, 10); print.conversionFactor(); } void cmdSetReadingInterval(char *args) { if (args) settings.readingIntervalMillis = strtol(args, NULL, 10); print.prog(PSTR("Reading Interval set to ")); print.ulng(settings.readingIntervalMillis); print.endl(); } void cmdSetEmaHalfLife(char *args) { if (args) settings.emaHalfLife = strtol(args, NULL, 10); print.prog(PSTR("EMA half life set to ")); print.pad(settings.emaHalfLife); print.endl(); } /**************************************************************************/ // Print out the current device ID /**************************************************************************/ void cmdReading(char *args) { unsigned long deltaMillis = elapsedTime(state.lastReading); float guessCPM = state.count * 60000 / deltaMillis; float guessDose = guessCPM * settings.conversionCoefficient; print.prog (PSTR("UpTime:")); print.upTime(); print.endl(); print.progln(PSTR("Current:")); print.prog (PSTR(" - time : ")); print.ulng(deltaMillis); print.progln(PSTR("msec")); print.prog (PSTR(" - counts : ")); print.pad(state.count); print.progln(PSTR("counts")); print.prog (PSTR(" - guessed : ")); print.flt(guessCPM,4); print.progln(PSTR("CPM")); print.prog (PSTR(" - guessed : ")); print.flt(guessDose,4); print.progln(PSTR("uS/hr")); print.progln(PSTR("Prior:")); print.prog (PSTR(" - reading : ")); print.flt(state.countsPerMinute,4); print.progln(PSTR("CPM")); print.prog (PSTR(" - reading : ")); print.flt(state.microsievertPerHour,4); print.progln(PSTR("uS/hr")); print.progln(PSTR("Average:")); print.prog (PSTR(" - reading : ")); print.flt(state.emaCountsPerMinute,4); print.progln(PSTR("CPM")); print.prog (PSTR(" - reading : ")); print.flt(state.emaMicrosievertPerHour,4); print.progln(PSTR("uS/hr")); } void cmdSettings(char *args) { print.progln(PSTR(" GIGEIR TUBE:")); print.prog (PSTR(" - tube : ")); print.pad(settings.tubeModel); print.tubeLabel(); print.endl(); print.prog (PSTR(" - conv : ")); print.flt(settings.conversionCoefficient,4); print.progln(PSTR(" uS/Hr = 1CPM")); print.prog (PSTR(" - interval: ")); print.ulng(settings.readingIntervalMillis); print.endl(); print.prog (PSTR(" - EMAHalf : ")); print.pad(settings.emaHalfLife); print.endl(); print.progln(PSTR(" Lan:")); print.prog (PSTR(" - lan : ")); print.pad((settings.lanDhcpFails & LAN_ON_MASK ) != 0); print.progln(PSTR(" (0=off)")); print.prog (PSTR(" - dhcp : ")); print.pad((settings.lanDhcpFails & DHCP_ON_MASK) != 0); print.progln(PSTR(" (0=off)")); print.prog (PSTR(" - fail lim: ")); print.pad(settings.lanDhcpFails & FAIL_COUNT_MASK); print.endl(); print.prog (PSTR(" - ip : ")); print.ip(settings.ipAddr); print.endl(); print.prog (PSTR(" - gateway : ")); print.ip(settings.gateway); print.endl(); print.prog (PSTR(" - subnet : ")); print.ip(settings.subnet); print.endl(); print.progln(PSTR(" UPLOAD:")); print.prog (PSTR(" - upip : ")); print.ip(settings.uploadIPAddr); print.endl(); print.prog (PSTR(" - upuri : ")); Serial.println(settings.uploadURI); } void cmdReset(char *args) { state.state = RESET; print.progln(PSTR("RESETING...\n")); } void cmdSave(char *args) { eeprom_write_block((byte *)&settings, 0, sizeof(settings_t)); print.progln(PSTR("Settings saved\n")); } void cmdHelp(char *args) { print.progln(PSTR(" GIGEIR TUBE:")); print.progln(PSTR(" - tube : set the tube type(autoset others)")); print.progln(PSTR(" - conv : set the 1CPM -> uS/Hr conversion")); print.progln(PSTR(" - inter: set the rate of reading compution/upload")); print.progln(PSTR(" - half : set the EMA half life")); print.progln(PSTR(" Lan:")); print.progln(PSTR(" - lan : turn lan on/off")); print.progln(PSTR(" - dhcp : turn dhcp on/off")); print.progln(PSTR(" - ip : set static ip")); print.progln(PSTR(" - fail : number of upload fails before reset")); print.progln(PSTR(" - gate : set static gateway")); print.progln(PSTR(" - snet : set static subnet")); print.progln(PSTR(" UPLOAD:")); print.progln(PSTR(" - upip : set a custom server ip")); print.progln(PSTR(" - upuri: set a custom server URI")); print.progln(PSTR(" ACTIONS:")); print.progln(PSTR(" - read : display a detailed reading")); print.progln(PSTR(" - sett : display the settings")); print.progln(PSTR(" - reset: reset the device")); print.progln(PSTR(" - save : save any changes to settings")); print.progln(PSTR("")); } //MAX cmds are 5 + 20 in args .. Ardiunos dont have masses of space #define CMD_MAX_LENGTH 25 typedef struct { const char* cmd; void (*func)(char*); } CmdEntry; void cmdParse(char* msg) { static CmdEntry cmdTable[] = { { "read", cmdReading }, { "sett", cmdSettings }, { "lan", cmdSetLanOn }, { "dhcp", cmdSetDhcpOn }, { "fail", cmdSetFailsUntilReset }, { "ip", cmdSetIpAddr }, { "gate", cmdSetGateway }, { "snet", cmdSetSubnet }, { "upip", cmdSetUploadIpAddr }, { "upuri", cmdSetUploadURI }, { "tube", cmdSetTube }, { "conv", cmdSetConversionFactor }, { "inter", cmdSetReadingInterval }, { "half", cmdSetEmaHalfLife }, { "reset", cmdReset }, { "save", cmdSave }, { NULL, NULL } }; char* cmd = strtok(msg, " "); char* arg = strtok(NULL, " "); if (cmd == NULL) { cmdHelp(NULL); return; } for (uint32_t i=0; cmdTable[i].cmd != NULL; i++) { if (!strcmp(cmd, cmdTable[i].cmd)) { (*cmdTable[i].func)(arg); return; } } cmdHelp(NULL); } void cmdPoll() { static char msg[CMD_MAX_LENGTH+1]; static uint8_t msg_idx = 0; bool process = false; while (Serial.available()) { if (msg_idx == 0) { print.endl(); print.prog(PSTR("CMD> ")); } char c = Serial.read(); if (c == '\r') { process = true; } else if ( c == '\b') { Serial.print(c); if (msg_idx > 0) msg_idx--; } else if(!process) { if (msg_idx < CMD_MAX_LENGTH) { // normal character entered. add it to the buffer Serial.print(c); msg[msg_idx++] = c; msg[msg_idx] = 0; } } else { //empty serial port... of junk after enter.. } } if (process) { print.endl(); Serial.println(msg); cmdParse(msg); msg_idx = 0; msg[msg_idx] = '\0'; } }
The Geiger is not always connected to a serial port. User input can also come in from the pull up switch added on the LCD shield and wired to the A1 pin. The button at the moment is setup to just toggle the LCD display modes. As you can see the lcd is refreshed in proxy from the button push via lcd_refresh this is so that other areas of the code can also trigger the lcd to change.
void handleButton() { static bool buttonPushState = false; static bool prevButtonPushState = false; // check if the pushbutton is pressed. // if it is, the buttonState is HIGH: buttonPushState = (digitalRead(BUTTON) == HIGH); if (buttonPushState != prevButtonPushState) { prevButtonPushState = buttonPushState; if (buttonPushState == true) { state.lcd_mode = (state.lcd_mode+1)%LCD_MAX_MODE; state.lcd_refresh = true; } } if (state.lcd_refresh) { state.lcd_refresh = false; print.lcd(); print.clr(); switch (state.lcd_mode) { case LCD_AVE_READING: // LCD output print.prog(PSTR("Av:")); print.flt(state.emaCountsPerMinute,1); print.endl(); print.flt(state.emaMicrosievertPerHour,4); break; case LCD_INST_READING: // LCD output print.prog(PSTR("Rw:")); print.flt(state.countsPerMinute,1); print.endl(); print.flt(state.microsievertPerHour,4); break; case LCD_STATE: print.prog(PSTR("St:")); switch (state.state) { case RESET: print.prog(PSTR("RST")); break; case SOFT_RESTART: print.prog(PSTR("SRST")); break; case INIT_DHCP: print.prog(PSTR("DHCP")); break; case OBTAIN_IP: print.prog(PSTR("IP")); break; case INIT_CLIENT: print.prog(PSTR("CLNT")); break; case NORMAL: print.prog(PSTR("NORM")); break; case CONNECT: print.prog(PSTR("CNCT")); break; case SEND_DATA: print.prog(PSTR("SEND")); break; case RESP_DATA: print.prog(PSTR("RESP")); break; default: print.prog(PSTR("????")); break; } print.endl(); print.prog(PSTR("Fails:")); print.pad(state.connFailCnt,2); break; case LCD_UPTIME: print.prog(PSTR("Up:")); print.upTime(false); break; } print.serial(); } }
Additionally for accutate computations of the radiation readings special care needs to be taken to compute the amount of time that has elapased between readings and updates of the clock. Poorly written clocking code will often drift quite dramatically, this destroying your readings accuracy. The key line is the "state.now = millis();" which samples and records the time atomically as possible, The calculations then account for timer overflows and computes the full time delta using the current and prior samples of the clock.
/**************************************************************************/ // calculate elapsed time. this takes into account rollover. /**************************************************************************/ unsigned long elapsedTime(unsigned long startTime) { unsigned long stopTime = millis(); if (startTime >= stopTime) return startTime - stopTime; else return (ULONG_MAX - (startTime - stopTime)); } void updateTime() { unsigned long prevNow = state.now; state.now = millis(); unsigned long deltaMillis; if (state.now >= prevNow) deltaMillis = state.now - prevNow; else deltaMillis = ULONG_MAX - (prevNow - state.now); state.upTimeMillis += deltaMillis; if ( state.upTimeMillis >= 1000) { state.upTimeSec += state.upTimeMillis / 1000; state.upTimeMillis = state.upTimeMillis % 1000; state.lcd_refresh |= (state.lcd_mode == LCD_UPTIME); if (state.upTimeSec >= 60) { state.upTimeMin += state.upTimeSec / 60; state.upTimeSec = state.upTimeSec % 60; if (state.upTimeMin >= 60) { state.upTimeHours += state.upTimeMin / 60; state.upTimeMin = state.upTimeMin % 60; if (state.upTimeHours >= 24) { state.upTimeDays += state.upTimeHours / 24; state.upTimeHours = state.upTimeHours % 24; } } } } }
The final interface on the device is the Ethernet port. Using the port is a bit complex but luckly the Arduino has a Ethernet lib complete with a Web client. As a result all I really need to do is generate a raw HTTP get request to a waiting server with the current raw reading for it to save. And at some point later read back its response.
/************************************************************************** Send data to server **************************************************************************/ void handleClient() { if(state.state == CONNECT) { if (client.connected()) { return; print.prog(PSTR("Disconnecting.")); client.stop(); } print.endl(); // Try to connect to the server print.prog(PSTR("Connecting.")); if (client.connect()) { print.progln(PSTR("Connected.")); state.lastConnectionTime = millis(); // clear the connection fail count if we have at least one successful connection state.connFailCnt = 0; state.state = SEND_DATA; state.lcd_refresh |= (state.lcd_mode == LCD_STATE); } else { state.connFailCnt++; print.prog(PSTR("Failed count:")); print.pad(state.connFailCnt); print.endl(); uint8_t fail_max = settings.lanDhcpFails & FAIL_COUNT_MASK; if((fail_max >0) && (state.connFailCnt > fail_max)) state.state = SOFT_RESTART; else state.state = NORMAL; state.lcd_refresh |= (state.lcd_mode == LCD_STATE); } } else if(state.state == SEND_DATA) { print.prog(PSTR("Upload.")); print.client(); print.prog(PSTR("GET ")); print.p(settings.uploadURI); print.prog(PSTR("?cpm=")); print.flt(state.countsPerMinute,1); print.prog(PSTR("&mSh=")); print.flt(state.microsievertPerHour,4); print.progln(PSTR(" HTTP/1.0")); print.endl(); print.serial(); state.state = RESP_DATA; state.lcd_refresh |= (state.lcd_mode == LCD_STATE); print.progln(PSTR("Uploaded.")); } else if(state.state == RESP_DATA) { //handle data transmission while(client.available()) { // Echo received strings to a host PC char c = client.read(); Serial.print(c); } if (!client.connected()) { Serial.println("disconnected."); client.stop(); state.state = NORMAL; state.lcd_refresh |= (state.lcd_mode == LCD_STATE); } } }
This bring us to the main body of the code. The Arduino's dont have a main per-say. They use 2 functions. The "setup" function is a run once piece of code just after reset. The "loop" function is basically an endless while(1) loop. The Chibi board is setup so that Ardiuno uses "wdt_enable(WDTO_8S)" as a hardware stop watch. If the stop watch runs out the hardware resets and starts fresh(In this case its was set to 8 secs). The later "wdt_reset()" restarts the timer freash for the next iteration of the loop. This way the Geiger can never get stuck in an unexpected state.
Also note the only interrupt that is registered in the system is the onPulse function. This is the most critical piece of the code, it counts the output from the Geiger tube. Note that its not protected from multiple interrupts, this is because the geiger tubes dead time is sufficently long enough(and the code is next to atomic anyway) for the code to complete and be ready for the next event.
The main loop can be summarized as
* handle reset
* handle serial cmds
* handle user feedback of radiative events
* compute new total uptime
* compute new reading if sampling period is over
* handle button input LCD output
* handle network setup
* handle web client communication
/**************************************************************************/ /*! On each falling edge of the Geiger counter's output, increment the counter and signal an event. The event can be used to do things like pulse a buzzer or flash an LED */ /**************************************************************************/ void onPulse() { state.count++; state.eventFlag = 1; } /**************************************************************************/ // main setup and loop... /**************************************************************************/ void setup() { delay(20); // fill in the UART file descriptor with pointer to writer. //fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE); // The uart is the standard output device STDOUT. //stdout = &uartout ; // set up the LCD's number of columns and rows: lcd.begin(8, 2); // Print a message to the LCD. lcd.print("Reset!"); Serial.begin(57600); print.progln(PSTR("Reseting..")); // tick to the usb console // put radio in idle state pinMode(RADIO_SELECT, OUTPUT); digitalWrite(RADIO_SELECT, HIGH); // disable radio chip select // reset the Wiznet chip pinMode(WIZNET_RESET_PIN, OUTPUT); digitalWrite(WIZNET_RESET_PIN, HIGH); delay(20); digitalWrite(WIZNET_RESET_PIN, LOW); delay(20); digitalWrite(WIZNET_RESET_PIN, HIGH); // get the device info eeprom_read_block((byte*)&settings, 0, sizeof(settings_t)); // init the control info memset(&state, 0, sizeof(state_t)); // // enable watchdog to allow reset if anything goes wrong wdt_enable(WDTO_8S); // Attach an interrupt to the digital pin and start counting // Note: // Most Arduino boards have two external interrupts: // numbers 0 (on digital pin 2) and 1 (on digital pin 3) attachInterrupt(GEIGER_INTERRUPT_PIN, onPulse, RISING); state.state = SOFT_RESTART; state.lcd_refresh |= (state.lcd_mode == LCD_STATE); state.count = 0; // kick the watch dog wdt_reset(); print.progln(PSTR("Reset..")); // tick to the usb console } void loop() { //polling loop.. very fast.. pfft // kick the dog only if we're not in RESET state. if we're in RESET // we will just let the device gracefully reset via the watchdag time out.. if (state.state != RESET) wdt_reset(); else state.lcd_refresh |= (state.lcd_mode == LCD_STATE); if (state.state == SOFT_RESTART) { state.state = INIT_DHCP; state.lcd_refresh |= (state.lcd_mode == LCD_STATE); } cmdPoll(); // user events -- poll the command line for any input if (state.eventFlag) { // Advertise a geiger event state.eventFlag = 0; // clear the event flag for later use print.prog(PSTR(".")); // tick to the usb console tone(PIN_SPKR, 1000); // beep the piezo speaker digitalWrite(PIN_LED, HIGH); // flash the LED delay(20); digitalWrite(PIN_LED, LOW); noTone(PIN_SPKR); // turn off the speaker pulse } // up time update -- to fast loosing to many millis updateTime(); if (settings.readingIntervalMillis == 0) return; // uS per hour calc and dump... unsigned long deltaMillis = elapsedTime(state.lastReading); if (deltaMillis > settings.readingIntervalMillis) { state.lastReading = millis(); // loop now cut down to about whatever the UPDATE has been changed to // count current cpm run.. state.countsPerMinute = (float)state.count * 60000.0 / (float)deltaMillis; state.count = 0; // Convert from cpm to õSv/h with the pre-defined coefficient state.microsievertPerHour = state.countsPerMinute * settings.conversionCoefficient; //EMA compute float alpha = 2.0/((float)(settings.emaHalfLife+1)); if (state.emaCountsPerMinute == 0) { state.emaCountsPerMinute = state.countsPerMinute; state.emaMicrosievertPerHour = state.microsievertPerHour; } else { state.emaCountsPerMinute = alpha*state.countsPerMinute + (1.0 - alpha)*state.emaCountsPerMinute; state.emaMicrosievertPerHour = alpha*state.microsievertPerHour + (1.0 - alpha)*state.emaMicrosievertPerHour; } print.endl(); print.prog(PSTR("Up: ")); print.upTime(); print.prog(PSTR(" ")); print.flt(state.countsPerMinute,1); print.prog(PSTR("CPM ")); print.flt(state.microsievertPerHour,4); print.prog(PSTR("uS/hr")); print.prog(PSTR(" EMAve: ")); print.flt(state.emaCountsPerMinute,1); print.prog(PSTR("CPM ")); print.flt(state.emaMicrosievertPerHour,4); print.prog(PSTR("uS/hr")); state.lcd_refresh |= (state.lcd_mode == LCD_AVE_READING) || (state.lcd_mode == LCD_INST_READING); } handleButton(); if ((settings.lanDhcpFails & LAN_ON_MASK) != 0) { if ((settings.lanDhcpFails & DHCP_ON_MASK) != 0) { //// boot and handle dhcp //if (state.state == INIT_DHCP) // { // EthernetDHCP.begin(macAddress, 1); // state.state = OBTAIN_IP; // state.lcd_refresh |= (state.lcd_mode == LCD_STATE); // } //else // handleDHCP(); } else { //no dhcp.. straight to client if ((state.state == INIT_DHCP) || (state.state == OBTAIN_IP)) { state.state = INIT_CLIENT; state.lcd_refresh |= (state.lcd_mode == LCD_STATE); //need to fill in these Ethernet.begin(macAddress, settings.ipAddr, settings.gateway, settings.subnet); print.progln(PSTR("Static Ethernet setup.")); // tick to the usb console } } if (state.state == INIT_CLIENT) { //boot and handle web client //client.stop(); client.init(settings.uploadIPAddr, 80); state.state = NORMAL; state.lcd_refresh |= (state.lcd_mode == LCD_STATE); } else if (state.state >= NORMAL) //only 1 per watch dog tick.. hence else if { if (deltaMillis > settings.readingIntervalMillis) { // send data if ready if (state.state == NORMAL) { state.state = CONNECT; state.lcd_refresh |= (state.lcd_mode == LCD_STATE); } } handleClient(); } } }
Sunday, October 16, 2011
Homemade Geiger Counter - Part 2 -- The hardware
Homemage Geiger Counter - Part 2 -- Hardware build
The main objective here is cheap and some what accurate.
Allow me to clarify this point. Reversed biased PN junctions(avalanche photo-diode et el) are very desirable as a Geiger counters
* They are cheap to mass produce using common manufacturing processes.
* If the circuitry and PN diodes are well chosen then the counter can be more accurate and faster than a Geiger tube or even scintillation crystals.
The problem is post Fukushima Geiger counters where in such high demand that anything that ticks in the presence of radiation was acceptable. Basically all of these are PN junction based devices, that are badly slapped together devices rushed out to a sellers market. What is worst is that these cheap devices where selling up of 500 USD a pop shortly after the event. A quick check of amazon shows me that the same models are now selling at around the 200 USD mark after having peaked at around 1500 USD!
I figure if I going to get crap it may as well be crap i have created for 1/15 of the price. So the device I have is around the 100 USD mark with usb and networking capabilities.
I used a Kited setup that the Tokyo hacker space member Akiba put together.
It involves an Ardiunio base board called Chibi(bottom in the picture). And a second shield they called a NetRAD(top in the above), which is basically a board with a LAN networking chip and the Geiger counter circuitry.
The final shield is an LCD and button. As you can see it was just hacked together with a cheap a prebuilt 600yen LCD board and a pull up switch.
The NetRADs specs are here http://tokyohackerspace.org/ja/project/tokyo-hackerspace-netrad-geiger-shield. And the Dev history of the board is here. http://www.tokyohackerspace.org/en/blog/tokyo-hackerspacerdtn-geiger-shield-dev-history. It makes for a very interesting read.
A fuller set of picture are visible here
I was hoping to do a full tear-down of the device but I simply can't seem to get the time.
I have also noticed the device seems to have a wide working range, a series of readings can vary by any thing upto 100% from the average. If this is due to the nature of radioactivity or something iffy in the Geigers power supply I don't know. Besides this the device is still excellent. The readings still average out to a stable reading over a 10min period.
The main objective here is cheap and some what accurate.
Allow me to clarify this point. Reversed biased PN junctions(avalanche photo-diode et el) are very desirable as a Geiger counters
* They are cheap to mass produce using common manufacturing processes.
* If the circuitry and PN diodes are well chosen then the counter can be more accurate and faster than a Geiger tube or even scintillation crystals.
The problem is post Fukushima Geiger counters where in such high demand that anything that ticks in the presence of radiation was acceptable. Basically all of these are PN junction based devices, that are badly slapped together devices rushed out to a sellers market. What is worst is that these cheap devices where selling up of 500 USD a pop shortly after the event. A quick check of amazon shows me that the same models are now selling at around the 200 USD mark after having peaked at around 1500 USD!
I figure if I going to get crap it may as well be crap i have created for 1/15 of the price. So the device I have is around the 100 USD mark with usb and networking capabilities.
I used a Kited setup that the Tokyo hacker space member Akiba put together.
It involves an Ardiunio base board called Chibi(bottom in the picture). And a second shield they called a NetRAD(top in the above), which is basically a board with a LAN networking chip and the Geiger counter circuitry.
The final shield is an LCD and button. As you can see it was just hacked together with a cheap a prebuilt 600yen LCD board and a pull up switch.
The NetRADs specs are here http://tokyohackerspace.org/ja/project/tokyo-hackerspace-netrad-geiger-shield. And the Dev history of the board is here. http://www.tokyohackerspace.org/en/blog/tokyo-hackerspacerdtn-geiger-shield-dev-history. It makes for a very interesting read.
A fuller set of picture are visible here
I was hoping to do a full tear-down of the device but I simply can't seem to get the time.
I have also noticed the device seems to have a wide working range, a series of readings can vary by any thing upto 100% from the average. If this is due to the nature of radioactivity or something iffy in the Geigers power supply I don't know. Besides this the device is still excellent. The readings still average out to a stable reading over a 10min period.
Wednesday, October 12, 2011
dyndns for an iphone server
Dynamic IP are common at residential properties and are quite a problem if you want your home server to be constantly reachable from outside. The solution is a dynamic IP tracking host like dyndns. Since im working from a hacked iphone a second problem is that perl doesnt seem to exist for it. So using a standard package is a bit of an issue. Here is a quick hacked up ruby daemon that can handle the dyndns updates.
Save this ruby script at /var/mobile/dyndns/update_ip.rb, (dont forget to chmod it to runable.)
UPDATE: It appears that dyndns gets peeved if you keep updating to the same IP. The code has been patched for it.
Then install the plist daemon
Paste in the code
And boot/load up the daemon and test
Remmber if u need to shut it down use
Save this ruby script at /var/mobile/dyndns/update_ip.rb, (dont forget to chmod it to runable.)
UPDATE: It appears that dyndns gets peeved if you keep updating to the same IP. The code has been patched for it.
#!/usr/bin/env ruby require 'base64' host = "example.dyndns.tv" auth = Base64.encode64('user:password') timeout = 600 while 1 output = "" puts "Discovering current IP.." # current ip resolve from checkip.dyndns.com IO.popen("telnet checkip.dyndns.com 80", "w+") do |pipe| output = pipe.readline puts output pipe.puts "GET / HTTP/1.1" pipe.puts "Host: checkip.dyndns.com" pipe.puts "Connection: close" pipe.puts "" pipe.puts "" puts "SENT ip query.." output = pipe.readlines.join(""); puts "---------------RESPONSE--------------------" puts output puts "--------------- END --------------------" pipe.close_write end if output =~ /.*Current IP Address: ([0-9]+.[0-9]+.[0-9]+.[0-9]+).*/ ip = $1 ip = ip.chomp puts "" puts "DETECTED At: " + ip end oldip = "" File.open('currentIP.txt', 'r') do |f| oldip = f.gets oldip = oldip.chomp end puts "PRIOR At: " + oldip if oldip == ip puts "MATCH sleeping..." else File.open('currentIP.txt', 'w') do |f| f.puts ip end exit puts "" puts "installing new IP..." #http://login:password@members.dyndns.org/nic/update?hostname=example.dyndns.tv&myip=99.99.99.99 IO.popen("telnet members.dyndns.org 80", "w+") do |pipe| output = pipe.readline puts output pipe.puts "GET /nic/update?hostname=" + host + "&myip=" + ip + " HTTP/1.1" pipe.puts "Host: members.dyndns.org" pipe.puts "User-Agent: codeslimjim/1.0 rubyscript/1.0" pipe.puts "Authorization: Basic " + auth pipe.puts "Connection: close" pipe.puts "" pipe.puts "" puts "SENT new ip.." output = pipe.readlines puts "---------------RESPONSE--------------------" puts output puts "--------------- END --------------------" pipe.close_write end end #timeout for x sec.. sleep timeout end
Then install the plist daemon
touch /Library/LaunchDaemons/com.dyndns.plist chmod go-wrx /Library/LaunchDaemons/com.dyndns.plist nano /Library/LaunchDaemons/com.dyndns.plist
Paste in the code
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.dyndns</string> <key>ProgramArguments</key> <array> <string>/var/mobile/dyndns/update_ip.rb</string> </array> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <true/> <key>UserName</key> <string>mobile</string> <key>WorkingDirectory</key> <string>/var/mobile/dyndns</string> </dict> </plist>
And boot/load up the daemon and test
launchctl load -w /Library/LaunchDaemons/com.dyndns.plist ps aux | grep dyndns
Remmber if u need to shut it down use
launchctl unload -w /Library/LaunchDaemons/com.dyndns.plist
Labels:
iPhone,
ruby,
system admin,
webserver
Saturday, October 1, 2011
Homemade Geiger Counter - Part 1 -- The basics
My Blog has been kind of quiet lately. the Reason is that I have been focused on building a Geiger counter. So here is what I have learnt.
Geiger basics. A Geiger counter is basically a simple event counting device. A Geiger tube connects to an amp, noise filter, comparator circuit and some form of counting system.
The Geiger tube is basically a outer housing, containing gas and a cathode and anode. The housing can be metal or even ultra thin foils, The gas is normally some inert gas and the cathode/anode can be made using anything from glass to metals.
The Geiger tube is charged to high voltage, this excites the gas inside the tube. In this state no current is yet flowing and the tube appears as an open circuit. When radiation is incident on the tube it must first travel through the outer housing, and strike one of the excited gases particles. This collision will tear free one or more electrons and these with then bump into other atoms and tear free more electrons and thus creating an avalanche effect.
Note carefully the limitations of the device,
1) the radiation must travel through the tubes outer housing and strike a gas atom. So the outer housing of the tube will absorb some particles without causing a detectable effect. Also particles can travel right thought the tube without causing an event. The size and thickness of the tube also effects how sensitive it is to radiation. But designers of the tube need to consider mechanical damage from shocks and contact with other items. Most directional tubes have a very thin wall made of mica that is very delicate. In summary there are a large range of tubes with different sensitivity to the various forms of radiations.
2) The main incident particle transfers energy to a single electron knocking it off the atom, this electron then hits another, these 2 electrons then knock more free and so on until there are no more electrons to be knocked free, this is the avalanche effect. This sea of freed electrons flows out the cathode, and eventually the gas recharges to high energy state. Now if the radiation was to knock a second electron free or second unconnected event was to hit the gases in the tube at the same time the tube is discharging then there is no way to tell the difference. This period is the tubes dead time. A Geiger counter that is taken into a high radiation area will suddenly go silent this is not broken it is because there are so many events hitting the tube that avalanche will never stop and the comparator will never detect the switch between Geiger's tubes quiescent state and the avalanche discharge state
3) This is a very simple device all it does is count events. Using it for anything other than the number counts requires some level of interpretation.
What these limitations mean is that what caused the event that was counted cannot be determined. If we assume your measuring air and it was an alpha particle then it is unlikely to penetrate skin and is therefore a lower dose. If it was gamma it will likely pass through your body and the wall behind u, thus giving it a bit more potential to cause some real damage to you. (Its not that simple but sufficient to highlight my point). So the conversion of counts to dosage is highly dependent on the type of tube your using, and the procedure that you used to measure it, what is causing the count (IE the spectrum of radioactive substances your measuring), and what the interaction of the substance is with the living being being dosed. In summary the devices dose conversion calibration needs to understood rather carefully, and the device used correctly.
Now Geiger counters don't necessarily need to be made with a a Geiger tube. A cheaper an less accurate design is to apply a similar circuit to a standard solar panel or LDR that is covered to prevent the entry of normal light but allow gamma radiation to still be incident on the panel or junction. These designs will measure radiation however, solar panel's and LDRs are not designed for radiation readings and so these designs have even more drawbacks than a standard Geiger counter.
Unfortunately post fukushima, Geiger counter prices sky rocketed and supplies became rarefied. These cheaper devices where affordable and available in a market with buyers who were desperate to get their hands on something/anything that can read the radiation. As a result these cheaper devices have been sold en-mass in japan and few customers realize the difference between what they have purchased and what the real item is. Keep in mind that the device does work so its technically not illegal to sell it as a radiation measurement instrument... Its just that if the customer is expecting a real device then they are getting hoodwinked by their own limited understanding. I have tried explaining this to Joe average before only it never seems to work. The discussion seems to always work out like explaining the difference between 24 Carat Gold and 12 Carat Gold to a person who only wants to understand that Gold is yellow.
Also note that Geiger tubes are far from the ultimate device to measure radiation a scintillator crystal is capable of producing varying colors and intensity of light as radiation strikes it, devices using these crystals are a level(and cost) far beyond the humble Geiger counter.
Geiger basics. A Geiger counter is basically a simple event counting device. A Geiger tube connects to an amp, noise filter, comparator circuit and some form of counting system.
The Geiger tube is basically a outer housing, containing gas and a cathode and anode. The housing can be metal or even ultra thin foils, The gas is normally some inert gas and the cathode/anode can be made using anything from glass to metals.
The Geiger tube is charged to high voltage, this excites the gas inside the tube. In this state no current is yet flowing and the tube appears as an open circuit. When radiation is incident on the tube it must first travel through the outer housing, and strike one of the excited gases particles. This collision will tear free one or more electrons and these with then bump into other atoms and tear free more electrons and thus creating an avalanche effect.
Note carefully the limitations of the device,
1) the radiation must travel through the tubes outer housing and strike a gas atom. So the outer housing of the tube will absorb some particles without causing a detectable effect. Also particles can travel right thought the tube without causing an event. The size and thickness of the tube also effects how sensitive it is to radiation. But designers of the tube need to consider mechanical damage from shocks and contact with other items. Most directional tubes have a very thin wall made of mica that is very delicate. In summary there are a large range of tubes with different sensitivity to the various forms of radiations.
2) The main incident particle transfers energy to a single electron knocking it off the atom, this electron then hits another, these 2 electrons then knock more free and so on until there are no more electrons to be knocked free, this is the avalanche effect. This sea of freed electrons flows out the cathode, and eventually the gas recharges to high energy state. Now if the radiation was to knock a second electron free or second unconnected event was to hit the gases in the tube at the same time the tube is discharging then there is no way to tell the difference. This period is the tubes dead time. A Geiger counter that is taken into a high radiation area will suddenly go silent this is not broken it is because there are so many events hitting the tube that avalanche will never stop and the comparator will never detect the switch between Geiger's tubes quiescent state and the avalanche discharge state
3) This is a very simple device all it does is count events. Using it for anything other than the number counts requires some level of interpretation.
What these limitations mean is that what caused the event that was counted cannot be determined. If we assume your measuring air and it was an alpha particle then it is unlikely to penetrate skin and is therefore a lower dose. If it was gamma it will likely pass through your body and the wall behind u, thus giving it a bit more potential to cause some real damage to you. (Its not that simple but sufficient to highlight my point). So the conversion of counts to dosage is highly dependent on the type of tube your using, and the procedure that you used to measure it, what is causing the count (IE the spectrum of radioactive substances your measuring), and what the interaction of the substance is with the living being being dosed. In summary the devices dose conversion calibration needs to understood rather carefully, and the device used correctly.
Now Geiger counters don't necessarily need to be made with a a Geiger tube. A cheaper an less accurate design is to apply a similar circuit to a standard solar panel or LDR that is covered to prevent the entry of normal light but allow gamma radiation to still be incident on the panel or junction. These designs will measure radiation however, solar panel's and LDRs are not designed for radiation readings and so these designs have even more drawbacks than a standard Geiger counter.
Unfortunately post fukushima, Geiger counter prices sky rocketed and supplies became rarefied. These cheaper devices where affordable and available in a market with buyers who were desperate to get their hands on something/anything that can read the radiation. As a result these cheaper devices have been sold en-mass in japan and few customers realize the difference between what they have purchased and what the real item is. Keep in mind that the device does work so its technically not illegal to sell it as a radiation measurement instrument... Its just that if the customer is expecting a real device then they are getting hoodwinked by their own limited understanding. I have tried explaining this to Joe average before only it never seems to work. The discussion seems to always work out like explaining the difference between 24 Carat Gold and 12 Carat Gold to a person who only wants to understand that Gold is yellow.
Also note that Geiger tubes are far from the ultimate device to measure radiation a scintillator crystal is capable of producing varying colors and intensity of light as radiation strikes it, devices using these crystals are a level(and cost) far beyond the humble Geiger counter.
Labels:
Embedded Software,
Geigers
Thursday, September 22, 2011
Too Smart to survive the company culture
The power of stupidity is Amazing. Its rampant in companies and I have had a serious headache trying to understanding what was going on.. I have finally come to understand it...
Here is a scenario based of it a recent event.
3 team members work for a department. They are given a blind choice:
A) perform an hard task and earn the company 120 dollars.
B) perform an easy task and earn the company 90 dollars.
* The choice is blind. you don't know what other people will choose until your committed.
* Both tasks take the same amount of time and become easier with more people.
* The tasks require at least 2 people to be doable.
* The reward doesn't scale with number of people.
* An employee who does not earn at least 30 dollars will be fired.
Now lets say that you the intelligent engineer realize the more your company earns the better off you are also. So your choice is A. Your assuming your rational team mates will also choose A the maximize the companies profit to 120. each of you would earn 40 dollars and keep your jobs.
Now what if your 2 team mates choose B. You have no support and can't complete your task.. you earn 0 and get fired. Your team mates each earn 45, far above the goal and get bonuses! And the company makes 90 dollars. but now they need to hire a replacement for you and have to pay out the bonuses to your team mates. So over all the company and you got screwed.. Furthermore the company has lost the good worker and reinforced the laziness of the other team mates... Also the new hire is subjected to this environmentally reinforced bad behavior and is high likely to fall prey to it on the next task...
Here is a scenario based of it a recent event.
3 team members work for a department. They are given a blind choice:
A) perform an hard task and earn the company 120 dollars.
B) perform an easy task and earn the company 90 dollars.
* The choice is blind. you don't know what other people will choose until your committed.
* Both tasks take the same amount of time and become easier with more people.
* The tasks require at least 2 people to be doable.
* The reward doesn't scale with number of people.
* An employee who does not earn at least 30 dollars will be fired.
Now lets say that you the intelligent engineer realize the more your company earns the better off you are also. So your choice is A. Your assuming your rational team mates will also choose A the maximize the companies profit to 120. each of you would earn 40 dollars and keep your jobs.
Now what if your 2 team mates choose B. You have no support and can't complete your task.. you earn 0 and get fired. Your team mates each earn 45, far above the goal and get bonuses! And the company makes 90 dollars. but now they need to hire a replacement for you and have to pay out the bonuses to your team mates. So over all the company and you got screwed.. Furthermore the company has lost the good worker and reinforced the laziness of the other team mates... Also the new hire is subjected to this environmentally reinforced bad behavior and is high likely to fall prey to it on the next task...
Labels:
job tips
Tuesday, August 23, 2011
How to locate and replace the next line
How to locate and replace the next line in a block of XML.. Ie find "findme" and then replace 80 on the next line with 100
perl -pe 'if($on == 1) { if(m/c>80/) {s/80/100/}; $on=0 }; $on=1 if m/findme/;' test.txt
<a> <b>findme</b> <c>80</c> </a> <a> <b>ignoreme</b> <c>80</c> </a>
Labels:
system admin
Thursday, August 4, 2011
Code Profiling classes
A simple code profiling class that will profile for the entry point "TIME()" to the closing curly "}". Enjoy
#include <iostream> #include <iomanip> #include <list> #include <time.h> #include <math.h> //test header #include <unistd.h> #include <stdlib.h> class TimerStats { public: typedef std::list<TimerStats*> TimerStatsList; typedef time_t TimeUnit; static TimerStatsList& statsList() { static TimerStatsList list; return list; } static void printAll(std::ostream& out) { TimerStatsList::iterator it; for(it = statsList().begin(); it != statsList().end(); it++) { out << *(*it); } } static TimeUnit getTime() { return time(NULL); } TimerStats(const char* file_, const uint32_t line_) : file (file_), line (line_), count (0), sum (0), sum_sqr(0), max (0), min (0) { statsList().push_back(this); } void addValue(double val) { count++; sum += val; sum_sqr += (val*val); if (count == 1) { max = val; min = val; } else { max = max > val ? max : val; min = min < val ? min : val; } } const char* file; const uint32_t line; uint32_t count; double sum; double sum_sqr; double max; double min; private: friend std::ostream& operator<<(std::ostream& out, TimerStats const& stats); }; std::ostream& operator<<(std::ostream& out, TimerStats const& stats) { double ave =0; double sdev=0; if (stats.count > 0) { ave = stats.sum/stats.count; sdev = stats.sum_sqr/stats.count - (ave*ave); sdev = sqrt(sdev); } out << " Count:" << std::setw(10) << stats.count << " Ave:" << std::setw(10) << ave << " Sdev:" << std::setw(10) << sdev << " Max:" << std::setw(10) << stats.max << " Min:" << std::setw(10) << stats.min << " Total:" << std::setw(10) << stats.sum << " " << stats.file << ":" << stats.line << "\n"; return out; } class Timer { public: Timer(TimerStats& stats_) : stats(stats_), start(TimerStats::getTime()) {} ~Timer() { stats.addValue(TimerStats::getTime()-start); } private: TimerStats& stats; TimerStats::TimeUnit start; }; #define TIME() static TimerStats stats##__LINE__(__FILE__,__LINE__); \ Timer timer##__LINE__(stats##__LINE__) int main(int argc, char const * const *argv) { { TIME(); for(int i= 0; i < 5; i++) { TIME(); for(int j= 0; j < 5; j++) { TIME(); // sleep(1); int k = rand() * rand(); std::cout << k << "\n"; while (k > 0) k--; } } } TimerStats::printAll(std::cout); }
Thursday, July 14, 2011
Data decimation
The some times u just cant send out 100s of MBs of data, hell sometimes you cant process it. Basically you need to decimate the results into a usable shape. The average joe understands averages and maximums. So break it down based on what you need to see for the current level of zoom.
function decimate($points, $bounds) { $lngcnt = 20; $latcnt = 20; $cdl = array(); foreach($points as &$p) { $lngper = ($p['long'] - $bounds[1])/($bounds[3] - $bounds[1]); $latper = ($p['lat'] - $bounds[0])/($bounds[2] - $bounds[0]); $latidx = intVal($latcnt*$latper); $lngidx = intVal($lngcnt*$lngper); $val = $p['cpm']; #create arrays if needed if(!isset($cdl[$lngidx])) { $cdl[$lngidx] = array(); } if(!isset($cdl[$lngidx][$latidx])) { $cdl[$lngidx][$latidx] = array(); $cp = &$cdl[$lngidx][$latidx]; $cp['sum'] = $val; $cp['cnt'] = 1; $cp['max'] = $val; $cp['max_long'] = $p['long']; $cp['max_lat'] = $p['lat']; # $cp['min'] = $val; # $cp['min_long'] = $p['long']; # $cp['min_lat'] = $p['lat']; } else { $cp = &$cdl[$lngidx][$latidx]; $cp['sum'] += $val; $cp['cnt']++; if ($val > $cp['max']) { $cp['max'] = $val; $cp['max_long'] = $p['long']; $cp['max_lat'] = $p['lat']; } # if ($val < $cp['min']) # { # $cp['min'] = $val; # $cp['min_long'] = $p['long']; # $cp['min_lat'] = $p['lat']; # } } } $res = array(); foreach($cdl as $lng => &$lngcdl) { foreach($lngcdl as $lat => &$cp) { if($cp['cnt'] > 0) { $pnt = array(); $pnt['cpm'] = $cp['sum']/$cp['cnt']; $pnt['long1'] = ($bounds[3] - $bounds[1])*$lng/$lngcnt + $bounds[1]; $pnt['lat1'] = ($bounds[2] - $bounds[0])*$lat/$latcnt + $bounds[0]; $pnt['long2'] = ($bounds[3] - $bounds[1])*($lng+1)/$lngcnt + $bounds[1]; $pnt['lat2'] = ($bounds[2] - $bounds[0])*($lat+1)/$latcnt + $bounds[0]; $pnt['max'] = $cp['max'] ; $pnt['max_long'] = $cp['max_long']; $pnt['max_lat'] = $cp['max_lat'] ; # $pnt['min'] = $cp['min'] ; # $pnt['min_long'] = $cp['min_long']; # $pnt['min_lat'] = $cp['min_lat'] ; array_push($res, $pnt); } } } return $res; }
Labels:
php
php sqlite3 queries.
Getting sqlite into php is easy as Here is an example query. Of course im forced to use sqlite in the first place because im running an iphone as a web server.
try { $db = new PDO("sqlite:data.sqlite"); } catch(PDOException $e) { echo $e->getMessage(); echo "<br><br>Database -- NOT -- loaded successfully .. "; die( "<br><br>Query Closed !!! $error"); } #away with ye hackers.. $bbox = sqlite_escape_string($_GET['b']); $bounds = split(" ", $bbox); if ( count($bounds) != 4) { die("[]"); } $sql = $db->prepare("SELECT * FROM readings WHERE lat >= ? AND long >= ? AND lat <= ? AND long <= ? LIMIT 1000"); if (! $sql->execute($bounds)) { die("[]"); } $query = $sql->fetchAll(PDO::FETCH_ASSOC); $decimated = decimate($query, $bounds); jsonOutput($decimated);
A php sqlite to json converter page
Next I build basically a quick converter page to convert an sqlite query result into json
function jsonOutput($query) { #json convert $ofirst = true; print "[\n"; foreach ($query as $row) { if($ofirst) $ofirst = false; else print ",\n"; $first = true; print "{"; foreach ($row as $key => $value) { if($first) $first = false; else print ","; if(is_numeric($value)) print $key . ': ' . $value; else print $key . ': "' . $value . '"'; } print "}"; } print "\n]\n"; }
And ajax test page
While building the radiation map prototype i put together a ajax data grabber here it is
<html> <head> <script type="text/javascript"> function loadData(str) { var xmlhttp; if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari xmlhttp=new XMLHttpRequest(); } else {// code for IE6, IE5 xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } xmlhttp.onreadystatechange=function() { if (xmlhttp.readyState==4 && xmlhttp.status==200) { document.getElementById("txtHint").innerHTML=xmlhttp.responseText; } } xmlhttp.open("GET","/safecast/data.php?lat=1&long=2",true); xmlhttp.send(); } </script> </head> <body> <div id="txtHint"> <button onclick="loadData('')">Get Data</button> </div> </body> </html>
Tuesday, July 12, 2011
perl to translate chars
$a =~ tr/[A-Z]/[a-z]/;
Try it with
perl -e '$a = "ABCD"; $a =~ tr/[A-Z]/[a-z]/; print $n;'$a;
Labels:
perl
Monday, July 11, 2011
google maps radiation maping for safecast
Just spent the day hacking together a google maps heat map mash up for safecast.org here is the prototype. Still needs alot of work... but it should be better than what they have at the moment..
Radiation Map Prototype type for Safecast.org
Will blog more about how it all works later.. But interesting thing to note is that its running of my iPhone web-server... very geeky...
Update: Ok.. its fallen over 4 times already... its an iphone calm down people...
Radiation Map Prototype type for Safecast.org
Will blog more about how it all works later.. But interesting thing to note is that its running of my iPhone web-server... very geeky...
Update: Ok.. its fallen over 4 times already... its an iphone calm down people...
Labels:
googlemaps,
javascript,
php,
webserver
Friday, June 24, 2011
Golden rules of Software engineering
- One mustn't imply the existing code is a reeking pile of spaghetti...
- Managers understand that no engineering feat is impossible, why cant Software engineers..
- All problems no matter how complex can be solved with meetings and PowerPoint slides...
- Bug trackers are for time tracking and customer communication not fixing problems in source code....
- When defining how an interface looks the words "Best and World Class" implies exactly what colors, layout and content is needed...
- Designers and Customers are gods. If they tell you to they want the confirm button to play Mozart at the top volume then by god it needs to play Mozart irrespective of what the user feedback says...
Sadly I have encountered each and every one of these items in my own personal experience.
Labels:
reality sucks
Tuesday, May 24, 2011
Iphone web server with php
I have an old iphone 3G that I upgraded to a 4G. Now I use it as my lowpower server. To do this you first need to jailbreak it and of course you need to have a home wifi setup. Serving private web pages is one of its uses. Here is the basic setup
Here is how to install lighttpd and php
I note that you need to keep the iPhone plugged into its charger to keep it connected to the wifi and alive. Otherwise it goes into a low power mode and shut off the wifi link, there by disconnecting your server.
I also iphones php package is rather limited in some aspects. For example create_socket doesn't work.
I also note that the user who is running the server seems bad.
Refer
http://www.esrun.co.uk/blog/lighttpd-php-on-the-iphone/
http://www.cyberciti.biz/tips/lighttpd-restrict-or-deny-access-by-ip-address.html
http://stackoverflow.com/questions/1181751/send-iphone-http-request-to-apache-php-webserver
http://arcoleo.org/dsawiki/Wiki.jsp?page=Autostart%20MySQL%20on%20Mac
Here is how to install lighttpd and php
- Jail break the iPhone using whatever setup you like
- Find the iPhones IP address in the wifi config area
- set your network IP to static.. and choose an IP at the end or out of your DHCP allocation range. (so that the DHCP doesnt keep moving your server around, nothing is worst for NAT setup..)
- set your network IP to static.. and choose an IP at the end or out of your DHCP allocation range. (so that the DHCP doesnt keep moving your server around, nothing is worst for NAT setup..)
- Change the default passwords
- SSH into the iPhone using the username "root" and password "alpine"
- "root"s original password is "alpine"
- "mobile"s original password is also "alpine"
- double check for other accounts in /etc/passwd
passwd #enter old/new passwords for root su mobile passwd #enter old/new passwords for mobile
- SSH into the iPhone using the username "root" and password "alpine"
- Remove password based ssh (makes it harder to hack in)
- copy/generate a ssh key from the entry machine.
- install the ssh key into the iPhone
- ssh into the phone (as mobile) and execute
mkdir -p ~/.ssh chmod 0700 ~/.ssh touch ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys cat >> ~/.ssh/authorized_keys #now paste in ssh key and hit crtl-D cat ~/.ssh/authorized_keys ls -al ~/.ssh/authorized_keys #confirm u see: -rw------- 1 mobile mobile ... /var/mobile/.ssh/authorized_keys
- Confirm login by ssh'ing in a diffrenet window
- BACKUP SSH KEY BEFORE CONTINUING. Its your way in from here on...
- Remove the password login.
su #enter root password nano /etc/ssh/sshd_config
- Now edit/add the following lines
PasswordAuthentication no AllowTcpForwarding no X11Forwarding no AllowUsers mobile
- Restart iPhone (maybe /usr/sbin/sshd is sufficent to restart the sshd)
- Check the setting by ssh'ing with the key/username. You will be prompted for login as: but it should reject before it asks a password.
- copy/generate a ssh key from the entry machine.
- Installing web server software
- run apt-get and install packages
su #enter root password apt-get install php apt-get install lighttpd
- if you are missing apt get you will need to open Cydia and install the "AptBackup" package
- run apt-get and install packages
- setup web software
- create the directory/files erc needed
mkdir -p /htdocs/site/ mkdir -p /htdocs/log/ chmod 777 /htdocs/log/ mkdir /etc/lighttpd/
- Configure the site.
nano /etc/lighttpd/lighttpd.conf
- Copy this into the file:
include "mod_fastcgi.conf" server.document-root = "/htdocs/site/" #server.port = 8080 server.username = "_sshd" server.groupname = "_sshd" server.bind = "localhost" server.tag ="lighttpd" server.errorlog = "/htdocs/log/error.log" accesslog.filename = "/htdocs/log/access.log" server.modules = ( "mod_access", "mod_accesslog", "mod_fastcgi", "mod_rewrite", "mod_auth", "mod_fastcgi" ) index-file.names = ( "index.html", "index.php" )
- Configure the php module.
nano /etc/lighttpd/mod_fastcgi.conf
- Copy this into the file:
fastcgi.server = ( ".php" => ( "localhost" => ( "bin-path" => "/usr/bin/php-cgi", "socket" => "/tmp/php.socket")))
- Setup a trial page
nano /htdocs/index.php
- Copy this into the file:
<? echo "Welcome:", $_SERVER['REMOTE_ADDR']; echo "< br >< br >Heres some uptime data:< br >"; passthru("uptime"); echo "< br >< br >Heres whats running:< br >"; passthru("ps | sed 's/$/< br >/'"); ?>
- Boot the server manually and check it all
lighttpd -f /etc/lighttpd/lighttpd.conf
- Open a browser and enter http://your_iphones_static_ip and you should see the trial page
- create the directory/files erc needed
- Right now lighttpd has to be manually started, it wont do it automatically.
- Set it to autoboot via a launchd control file at /Library/LaunchDaemons/com.lighttpd.plist the launchd process will start it and keep it running if it should crash or be killed.
-
touch /Library/LaunchDaemons/com.lighttpd.plist chmod go-wrx /Library/LaunchDaemons/com.lighttpd.plist nano /Library/LaunchDaemons/com.lighttpd.plist
- Paste in the contents
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.lighttpd</string> <key>ProgramArguments</key> <array> <string>/usr/sbin/lighttpd</string> <string>-f</string> <string>/etc/lighttpd/lighttpd.conf</string> </array> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <true/> <key>UserName</key> <string>root</string> <key>WorkingDirectory</key> <string>/htdocs</string> </dict> </plist>
- load it with(and check)
launchctl load -w /Library/LaunchDaemons/com.lighttpd.plist ps aux | grep light
- You can shut it down again with:
sudo launchctl unload -w /Library/LaunchDaemons/com.lighttpd.plist
- Set it to autoboot via a launchd control file at /Library/LaunchDaemons/com.lighttpd.plist the launchd process will start it and keep it running if it should crash or be killed.
I note that you need to keep the iPhone plugged into its charger to keep it connected to the wifi and alive. Otherwise it goes into a low power mode and shut off the wifi link, there by disconnecting your server.
I also iphones php package is rather limited in some aspects. For example create_socket doesn't work.
I also note that the user who is running the server seems bad.
Refer
http://www.esrun.co.uk/blog/lighttpd-php-on-the-iphone/
http://www.cyberciti.biz/tips/lighttpd-restrict-or-deny-access-by-ip-address.html
http://stackoverflow.com/questions/1181751/send-iphone-http-request-to-apache-php-webserver
http://arcoleo.org/dsawiki/Wiki.jsp?page=Autostart%20MySQL%20on%20Mac
Labels:
iPhone,
system admin,
webserver
quick wake on lan script with netcat
An WOL packet is simply the 6 bytes of FF followed by the 6 byte MAC address repeated 16 times.
So to script it first generate the wake packet.. Im assuming the mac is AA:BB:CC:DD:EE:FF and Im storing the packet so I can use it on a machine without xxd..hence the file dump.
Then send out the wake packet with Netcat. You can send it directly to the server or to your routers broadcast address it should goto either port 7 or 9. For the example i choose 192.168.1.200 and port 7.
So to script it first generate the wake packet.. Im assuming the mac is AA:BB:CC:DD:EE:FF and Im storing the packet so I can use it on a machine without xxd..hence the file dump.
ETHER="aa:bb:cc:dd:ee:ff" ETHER2=`echo $ETHER | sed "s/://g"` ETHER3="${ETHER2}${ETHER2}${ETHER2}${ETHER2}" ETHER4="FFFFFFFFFFFF${ETHER3}${ETHER3}${ETHER3}${ETHER3}" echo ${ETHER4} | xxd -r -p > wake.packet
Then send out the wake packet with Netcat. You can send it directly to the server or to your routers broadcast address it should goto either port 7 or 9. For the example i choose 192.168.1.200 and port 7.
netcat -c -v -u -n -x -p 80 192.168.1.200 7 < wake.packetThe contents of the packet should looks like this:
$ xxd wake.packet 0000000: ffff ffff ffff aabb ccdd eeff aabb ccdd ................ 0000010: eeff aabb ccdd eeff aabb ccdd eeff aabb ................ 0000020: ccdd eeff aabb ccdd eeff aabb ccdd eeff ................ 0000030: aabb ccdd eeff aabb ccdd eeff aabb ccdd ................ 0000040: eeff aabb ccdd eeff aabb ccdd eeff aabb ................ 0000050: ccdd eeff aabb ccdd eeff aabb ccdd eeff ................ 0000060: aabb ccdd eeff ......
Labels:
system admin,
wol
Post iPhone UI design
UI design is always one of the most annoying and unportable tasks a programmer faces. (Aside from the problems with "professional" designs that have more photoshop effects that common sense). Todays devices are changing drastically the iPhone was the linchpin on this. It has fundamentally redefined the human machine interface. Mouse point and click interfaces now need to handle the the finger pinch and flick. Static window layouts wont live much longer in a world of rotatable and portable hardware.
The problem boils down to the fact that every joe and his dog has his own UI implementation. And after reviewing several of these in a post iPhone/Android world I have come the conclusion that they just are not going to survive beyond a few years from now. Now more that ever the clean separation between Model/Controller/View needs to be handled or your code is going to get outdated and become a maintenance time sucker.
I think Androids Fragment idea is the way to go on this. Release the constraints on the placement/layout and reconsider your UI from the perspective of a set of fragments or UI chunks that are correlated and interacting. Think dynamic and fluid layouts. This way it allows the segments to be shuffled around to meet the needs or your next port and code reuse.
While doing all this its best to stay as farway as you can from the Frameworks Implementation specific classes. Especially the pointless rewrite/rebuild of the string and networking class that seems to be in every framework. Wrap them if you must use them but don't let it them anywhere near your core implementation.
Refer:
http://developer.android.com/videos/index.html#v=WGIU2JX1U5Y
The problem boils down to the fact that every joe and his dog has his own UI implementation. And after reviewing several of these in a post iPhone/Android world I have come the conclusion that they just are not going to survive beyond a few years from now. Now more that ever the clean separation between Model/Controller/View needs to be handled or your code is going to get outdated and become a maintenance time sucker.
I think Androids Fragment idea is the way to go on this. Release the constraints on the placement/layout and reconsider your UI from the perspective of a set of fragments or UI chunks that are correlated and interacting. Think dynamic and fluid layouts. This way it allows the segments to be shuffled around to meet the needs or your next port and code reuse.
While doing all this its best to stay as farway as you can from the Frameworks Implementation specific classes. Especially the pointless rewrite/rebuild of the string and networking class that seems to be in every framework. Wrap them if you must use them but don't let it them anywhere near your core implementation.
Refer:
http://developer.android.com/videos/index.html#v=WGIU2JX1U5Y
Labels:
UI design
Sunday, May 22, 2011
forcing i386 installs on 64bit
Sometimes you just cant find a 64 bit version of stuff.. force that 32 bit deb to install with.
sudo dpkg --force-architecture -i downloaded_package.deb
Labels:
system admin
rtti to traits for auto uuiding classes
There are times when you want automatically UID or index a class. The rtti is string based and I am assuming that simple Hashing of of that string is not sufficient. So here is a quick hack up of trait class to achieve the result.
Output looks like:
#include <iostream> #include <stdint.h> #include <map> #include <typeinfo> class RttiCasterBase { private: class RttiRegFactory { private: typedef std::map<std::string, uint32_t> RttiIDMap; //0 is the bad marker.. uint32_t g_uid; RttiIDMap rttiIDMap; RttiRegFactory() : g_uid(1) {} public: static RttiRegFactory& instance() { static RttiRegFactory instance_; return instance_; } uint32_t getID(std::string rttiName) { if (rttiIDMap.find(rttiName) != rttiIDMap.end()) return rttiIDMap[rttiName]; rttiIDMap[rttiName] = g_uid; return g_uid++; } }; protected: static uint32_t lookup(std::string rttiID) { return RttiRegFactory::instance().getID(rttiID); } public: RttiCasterBase() {} virtual ~RttiCasterBase() {} virtual uint32_t uid() = 0; virtual std::size_t size() = 0; virtual std::string rttiID() = 0; }; template<typename T> class RttiCasterImp : public RttiCasterBase { public: typedef T Type; uint32_t uid_; RttiCasterImp() : RttiCasterBase() { uid_ = RttiCasterBase::lookup(typeid(T).name()); } ~RttiCasterImp() {} virtual uint32_t uid() { return uid_; } virtual std::size_t size() { return sizeof(T); } virtual std::string rttiID() { return typeid(T).name(); } T* cast(void* buf) { return reinterpret_cast<T*>(buf); } void print(std::ostream& out) { out << "uid: " << uid() << std::endl << "size: " << size() << std::endl << "rttiID: " << rttiID() << std::endl << std::endl; } }; class A {}; class B { int var; }; class C { char stuff[10]; }; int main(int argc, char const * const *argv) { RttiCasterImp<A> InfoA; RttiCasterImp<B> InfoB; RttiCasterImp<C> InfoC; RttiCasterImp<B> InfoD; InfoA.print(std::cout); InfoB.print(std::cout); InfoC.print(std::cout); InfoD.print(std::cout); }
Output looks like:
$ a.exe uid: 1 size: 1 rttiID: 1A uid: 2 size: 4 rttiID: 1B uid: 3 size: 10 rttiID: 1C uid: 2 size: 4 rttiID: 1B
Social circle.
Google has put out a new cross-referencing set of pages. They basically tie your profile page to your know friends and contacts. Check it out for your self.
http://www.google.com/s2/search/social#socialconnections
http://www.google.com/s2/search/social#socialconnections
Labels:
bloging
cleaning up ubuntus file usage
FIRST OF ALL get into the apt-get and clear out the apps you dont want. Then purge, autoclean, autoremove, orphan remove all the remain pieces until nothing is left
Heres the basic clean up (plus some setup)
When you take out packages the best way is to --purge or autoremove them
Then execute gtkorphan and keep killing unneed deps until it cant find any.
The Disk Usage Analyzer to sort out the biggest space wasters. Its found at Applications->Accessories->Disk Usage Analyzer.
There is also the computer janitor. Its at System > Administration > Computer Janitor.
And you can also hunt down the dead log files and other crap in you system with fslint.
You may need to loop around the whole process a few times to get it really clear
Several useless packages that tend to be installed by default are(sorry list is very short I got hacking before I thought about writing it down);
Heres the basic clean up (plus some setup)
sudo apt-get -y install gtkorphan logrotate localepurge fslint sudo apt-get autoremove sudo apt-get clean all sudo apt-get autoclean all sudo localepurge dpkg -l | grep '^rc'| cut -d' ' -f3 | xargs dpkg -P
When you take out packages the best way is to --purge or autoremove them
sudo apt-get --purge remove bluez-utils bluez-gnome sudo apt-get autoremove bluez-utils bluez-gnome
Then execute gtkorphan and keep killing unneed deps until it cant find any.
The Disk Usage Analyzer to sort out the biggest space wasters. Its found at Applications->Accessories->Disk Usage Analyzer.
There is also the computer janitor. Its at System > Administration > Computer Janitor.
And you can also hunt down the dead log files and other crap in you system with fslint.
You may need to loop around the whole process a few times to get it really clear
Several useless packages that tend to be installed by default are(sorry list is very short I got hacking before I thought about writing it down);
- gnome-pilot
- mesu-utils
Labels:
system admin
PHP: getting the users IP address
$_SERVER['REMOTE_ADDR'];
Labels:
php
c++ chomping white space
A Bit of code to chomp strings in c++
#include <string> #include <iostream> int main() { std::string str; getline(std::cin, str); std::cout << "'" << str << "'" << std::endl; std::string::size_type pos = str.find_last_not_of("\n \t"); if(pos != std::string::npos) { str = str.substr(0, pos+1); } std::cout << "'" << str << "'" << std::endl; }
Labels:
c++
Quick way list your LAN machines
Here is a quick way to grab the Ethernet MACs and IPs of the machines that are up on your LAN
arp -a
Labels:
system admin
Suming a list of numbers of the shell
Manipulating lists on the shell can be done with awk. The most common one I encounter is summing numbers. Here is a quick bit of shell magic to sum up the size of all log files below a certian dictionary. Awk has much more power than this however.
find . -type f | grep "\.log" | xargs ls -al | sed "s/ */ /g" | cut -d " " -f 5 | awk -F: '{total+=$1} END{print total}'
Labels:
shell,
system admin
Friday, May 6, 2011
Shell repeat/do/while for a count
Gahh.. simple thing but annoying its for bash shell
i=245; while [ $i -ne 33 ]; do echo $i; i=`expr $i - 1`; done
Wednesday, April 27, 2011
Earthquakes in japan between
Quick video showing earthquakes in Japan between 9 March and 14 March. 1 hour ~ 1 second. Big one is around 1:17.
Labels:
earthquake
Suming a list of numbers
cat numbers.txt | awk -F: '{total+=$1} END{print total}'
Labels:
shell
Monday, March 14, 2011
Japan Earthquake Crisis and Info site in english and Japanese
Japan crisis response page on google http://www.google.com/crisisresponse/japanquake2011.html#resources"
If you wish to donate go to the google page and donate to the japanese red cross.
If your in japan the power will be out in a set schedule around the effected areas as it is diverted to cover the missing power planets
The info is here http://www.tepco.co.jp/index-j.html but the site is very overloaded
The groups are as follows
第1グループ 6:20~10:00 16:50~20:30
第2グループ 9:20~13:00 18:20~22:00
第3グループ 12:20~16:00
第4グループ 13:50~17:30
第5グループ 15:20~19:00
The number of missing is over 20000, dont be idiots with the sites.
The news is says they are looking for medical staff, medical supplies and portable electric generators.
They are also cooling for basic food water, cooked rice(they need to conserve water) and bread.
The Japanese metorology infomation site lists the series of aftershocks we have had here (as you can see there are over 30).
http://www.jma.go.jp/en/quake/quake_sindo_index.html
If you wish to donate go to the google page and donate to the japanese red cross.
If your in japan the power will be out in a set schedule around the effected areas as it is diverted to cover the missing power planets
The info is here http://www.tepco.co.jp/index-j.html but the site is very overloaded
The groups are as follows
第1グループ 6:20~10:00 16:50~20:30
第2グループ 9:20~13:00 18:20~22:00
第3グループ 12:20~16:00
第4グループ 13:50~17:30
第5グループ 15:20~19:00
The number of missing is over 20000, dont be idiots with the sites.
The news is says they are looking for medical staff, medical supplies and portable electric generators.
They are also cooling for basic food water, cooked rice(they need to conserve water) and bread.
The Japanese metorology infomation site lists the series of aftershocks we have had here (as you can see there are over 30).
http://www.jma.go.jp/en/quake/quake_sindo_index.html
Labels:
earthquake
Saturday, March 12, 2011
Earthquake Marunouchi building Tokyo Station March 11, 2011
This is the initial quake. On the 19th floor. The buildings seismic meter picked it up as magnitude 4.5. I was in the express elevator on the way up and got kicked out about 10-15 seconds into it, felt like minutes. The sounds you are hearing on the video are the elevators wires and the building walls groaning. The guys you can see are walking back and forth because the building is moving around us like a jumping castle or fair ride. Hence the crappy filming.
This is the first after shock. and im on the 22 flour now. They have continued all night I have counted 6 at least. The buildings seismic meter picked it up as little over magnitude 4. I was back at my desk by this point. The building across the road is about 70-100 meters away. As you can see its moving and we are moving. Not nearly as bad as the first but still its clear to see the effects of quake on a high rise
Tuesday, March 1, 2011
Debuging random segvs
This code is a simple and Focused segv catcher, it is designed for transient and random errors, the ones where you never know when or if they are going to happen. The idea is to use a local varable to scope the monitered area and keep tabs of the last line of code with the jump function. There is a streaming mech to gather input params and other data along the way(ie you can log the local vars in it after the fact). Then when a segv actually happens it wakes up and informs you of where it feel over and what the logged data was.
#include <csignal> #include <iostream> #include <stdlib.h> #include <sstream> namespace SigCatchDebug { void (*original)(int); bool installed = false; std::string file_; int line_ = 0; std::stringstream data; void debug_sig(int type) { if(line_ != 0) { std::cout << "Signal Caught!!!" << std::endl << "last trace loc:" << file_ << ":" << line_ << std::endl << "----- data was ---- " << std::endl << data.str() << std::endl << std::endl; } if(original != SIG_DFL || original != SIG_IGN) (*original)(type); std::exit(EXIT_FAILURE); //<<-- this really needs to be here! } class AutoScope { public: AutoScope(const char* file, const int line) { if(!installed) { installed = true; original = std::signal (SIGSEGV,debug_sig); if (original == SIG_ERR) { std::cout << "signal implosion" << std::endl; } //std::cout << "signal installed" << std::endl; } file_ = file; line_ = line; data.str(""); } template<typename T> inline AutoScope& operator<<(const T& t) throw() { try { data << t; } catch (...) {} return *this; } ~AutoScope() { //std::cout << "signal disable" << std::endl; line_ = 0; } void jump(int line) { line_ = line; } }; } void evil_random_crashing_func() { static count = 0; char** test = NULL; SigCatchDebug::AutoScope sigScope(__FILE__,__LINE__); sigScope << "test: " << test << "\n" << "count: " << count; sigScope.jump(__LINE__); if(count == 3) test[0] = "ABCDEFG"; sigScope.jump(__LINE__); if(count == 4) std::cout << "evil_random_crashing_func: " << test[0] << std::endl; std::cout << "Evil " << count << std::endl; count++; } void evil_random_crashing_func2() { char** test = NULL; test[0] = "ABCDEFG"; std::cout << "evil_random_crashing_func2: " << test[0] << std::endl; } int main(int argc, char const * const *argv) { try { evil_random_crashing_func(); evil_random_crashing_func(); //evil_random_crashing_func2(); evil_random_crashing_func(); evil_random_crashing_func(); } catch (std::exception const &e) { std::cout << "Unexpected exception: " << e.what() << std::endl; } catch (...) { std::cout << "Unexpected exception" << std::endl; } return 0; }
Subscribe to:
Posts (Atom)