├── .gitignore ├── README.md ├── hardware ├── LICENSE-CERN-OHL.txt ├── PiGI-Prototype-Board-V1.0-PCB-only.jpg ├── PiGI-Prototype-Board-V1.0-on-Pi.jpg ├── PiGI-Prototype-Board-V1.0-perspective.jpg ├── PiGI-Prototype-Board-V1.0.jpg ├── PiGI-V1.0-bom.txt ├── PiGI-V1.0.brd ├── PiGI-V1.0.sch ├── PiGI-V1.1.brd └── PiGI-V1.1.sch └── software ├── LICENSE-GPLv3.txt ├── README.md ├── conf └── default.cfg ├── examples ├── c │ ├── README.md │ ├── counterd.c │ ├── counterd1.c │ ├── counterd2.c │ ├── counterd3.c │ └── counterd4.c └── python │ ├── entropygeiger.py │ └── geiger.py ├── init.d ├── README.txt └── pyGIserver ├── log └── .keep ├── public ├── assets │ ├── css │ │ ├── jqtouch.css │ │ ├── jqtouch.min.css │ │ └── webGI.css │ ├── font │ │ ├── arimo-regular.eot │ │ ├── arimo-regular.ttf │ │ ├── arimo-regular.woff │ │ ├── digital-7-webfont.eot │ │ ├── digital-7-webfont.svg │ │ ├── digital-7-webfont.ttf │ │ ├── digital-7-webfont.woff │ │ ├── digital-7.eot │ │ ├── digital-7.ttf │ │ ├── digital-7.woff │ │ ├── digital-7_mono.eot │ │ ├── digital-7_mono.ttf │ │ ├── digital-7_mono.woff │ │ ├── digital-7_mono_italic.eot │ │ ├── digital-7_mono_italic.ttf │ │ ├── digital-7_mono_italic.woff │ │ ├── sourcesanspro-regular.eot │ │ ├── sourcesanspro-regular.ttf │ │ ├── sourcesanspro-regular.woff │ │ ├── titillium-regular.eot │ │ ├── titillium-regular.ttf │ │ ├── titillium-regular.woff │ │ ├── webgi.eot │ │ ├── webgi.svg │ │ ├── webgi.ttf │ │ └── webgi.woff │ ├── img │ │ ├── apollo-badge.png │ │ ├── apollo-ng-badge.svg │ │ ├── grips.svg │ │ ├── orm_bw.svg │ │ ├── orm_col.svg │ │ ├── orm_new.svg │ │ ├── orm_new_dark.svg │ │ ├── orm_new_dark_web.svg │ │ ├── orm_new_inv.svg │ │ └── panel_bg.png │ ├── js │ │ ├── dygraph-combined-dev.js │ │ ├── dygraph-combined.js │ │ ├── jqtouch.js │ │ ├── jqtouch.min.js │ │ ├── webGI.js │ │ ├── webGI_MODULE.js │ │ ├── webGI_alert.js │ │ ├── webGI_geo.js │ │ ├── webGI_history.js │ │ ├── webGI_livechart.js │ │ ├── webGI_options.js │ │ ├── webGI_status.js │ │ ├── webGI_ticker.js │ │ ├── webGI_tracer.js │ │ ├── webGI_websocket.js │ │ ├── zepto.js │ │ └── zepto.min.js │ └── snd │ │ ├── tick.mp3 │ │ ├── tock.wav │ │ └── ui-bell.mp3 ├── favicon.ico └── index.html ├── pyGI ├── __init__.py ├── configurator.py ├── entropygenerator.py ├── geigerclient.py ├── geigercounter.py ├── geigerlog.py └── geigerserver.py └── pyGIserver.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | \#* 4 | .#* 5 | *.swp 6 | .DStore/ 7 | thumbs.db 8 | storage/profiles 9 | config.py 10 | *.b#* 11 | *.s#* 12 | *.db 13 | *.db.idx-* 14 | entropy.bin 15 | local.cfg 16 | dynamic.cfg 17 | uuid.cfg 18 | pyGI.log 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PiGI - A Raspberry Pi Geiger-Mueller Interface 2 | 3 | ![Image](https://apollo.open-resource.org/_media/lab:pigi-prototype-board-v1.0-on-pi.jpg) 4 | 5 | The PiGI is built as a ready-to-go drop-in module for the Raspberry Pi to transform it 6 | into a versatile geiger counter to measure/monitor radioactivity. It will generate the 7 | required high voltage the counting tubes need to operate and it will safely invert the 8 | counting impulses to a falling edge, detectable by a GPIO Pin on the PI. But it's also 9 | designed in such a universal way in order to be very hackable. Basically it can be 10 | connected to any processing system that can detect falling edges like: 11 | 12 | * Arduino 13 | * ATMega 14 | * PIC 15 | * Other embedded Linux ARM/MIPS systems with GPIO Inputs (GNUBLIN, Netus G20 etc.) 16 | 17 | ## Specifications 18 | 19 | * 40x43mm board 20 | * Very low energy consumption (<2mA @ 0.09uSv/h local dose rate) 21 | * Cathode counting 22 | * Low BOM count / small footprint 23 | * Very cheap design: Prototype costs per board -> EUR15 / High Volume Production < 10 EUR 24 | * Dual stackable for low/high dosis counting with 2 tubes 25 | * Open-Source Hardware/Software 26 | 27 | ## Hardware 28 | 29 | * Schematics 30 | * Board designs 31 | * Released under CERN OHL 1.2 32 | 33 | ![Image](https://apollo.open-resource.org/_media/lab:pigi-prototype-board-v1.0.jpg) 34 | 35 | ## Software 36 | 37 | ### Features ### 38 | 39 | * Live Status 40 | * Live (15min/60min/24h) Graphs 41 | * Analog gauge 42 | * Ion Trace Visualizer 43 | * History 44 | * Tick Simulator (For show and development) 45 | * Hardware RNG entropy generator 46 | * More to come 47 | * Released under GPL V3 48 | 49 | ### Screenshots ### 50 | 51 | #### Main instrument panel 52 | ![Image](https://apollo.open-resource.org/_media/lab:webgi-mainpanel.jpg) 53 | #### History instrument panel 54 | ![Image](https://apollo.open-resource.org/_media/lab:webgi-historypanel.jpg) 55 | #### Ion trace visualizer 56 | ![Image](https://apollo.open-resource.org/_media/lab:webgi-tracevisualizer.jpg) 57 | 58 | ## Website & Contact 59 | 60 | [https://apollo.open-resource.org/lab:pigi](https://apollo.open-resource.org/lab:pigi) 61 | -------------------------------------------------------------------------------- /hardware/LICENSE-CERN-OHL.txt: -------------------------------------------------------------------------------- 1 | CERN Open Hardware Licence v1.2 2 | 3 | Preamble 4 | 5 | Through this CERN Open Hardware Licence ("CERN OHL") version 1.2, CERN 6 | wishes to provide a tool to foster collaboration and sharing among 7 | hardware designers. The CERN OHL is copyright CERN. Anyone is welcome 8 | to use the CERN OHL, in unmodified form only, for the distribution of 9 | their own Open Hardware designs. Any other right is reserved. Release 10 | of hardware designs under the CERN OHL does not constitute an 11 | endorsement of the licensor or its designs nor does it imply any 12 | involvement by CERN in the development of such designs. 13 | 14 | 1. Definitions 15 | 16 | In this Licence, the following terms have the following meanings: 17 | 18 | “Licence” means this CERN OHL. 19 | 20 | “Documentation” means schematic diagrams, designs, circuit or circuit 21 | board layouts, mechanical drawings, flow charts and descriptive text, 22 | and other explanatory material that is explicitly stated as being made 23 | available under the conditions of this Licence. The Documentation may 24 | be in any medium, including but not limited to computer files and 25 | representations on paper, film, or any other media. 26 | 27 | “Documentation Location” means a location where the Licensor has 28 | placed Documentation, and which he believes will be publicly 29 | accessible for at least three years from the first communication to 30 | the public or distribution of Documentation. 31 | 32 | “Product” means either an entire, or any part of a, device built using 33 | the Documentation or the modified Documentation. 34 | 35 | “Licensee” means any natural or legal person exercising rights under 36 | this Licence. 37 | 38 | “Licensor” means any natural or legal person that creates or modifies 39 | Documentation and subsequently communicates to the public and/ or 40 | distributes the resulting Documentation under the terms and conditions 41 | of this Licence. 42 | 43 | A Licensee may at the same time be a Licensor, and vice versa. 44 | 45 | Use of the masculine gender includes the feminine and neuter genders 46 | and is employed solely to facilitate reading. 47 | 48 | 2. Applicability 49 | 50 | 2.1. This Licence governs the use, copying, modification, 51 | communication to the public and distribution of the Documentation, and 52 | the manufacture and distribution of Products. By exercising any right 53 | granted under this Licence, the Licensee irrevocably accepts these 54 | terms and conditions. 55 | 56 | 2.2. This Licence is granted by the Licensor directly to the Licensee, 57 | and shall apply worldwide and without limitation in time. The Licensee 58 | may assign his licence rights or grant sub-licences. 59 | 60 | 2.3. This Licence does not extend to software, firmware, or code 61 | loaded into programmable devices which may be used in conjunction with 62 | the Documentation, the modified Documentation or with Products, unless 63 | such software, firmware, or code is explicitly expressed to be subject 64 | to this Licence. The use of such software, firmware, or code is 65 | otherwise subject to the applicable licence terms and conditions. 66 | 67 | 3. Copying, modification, communication to the public and distribution 68 | of the Documentation 69 | 70 | 3.1. The Licensee shall keep intact all copyright and trademarks 71 | notices, all notices referring to Documentation Location, and all 72 | notices that refer to this Licence and to the disclaimer of warranties 73 | that are included in the Documentation. He shall include a copy 74 | thereof in every copy of the Documentation or, as the case may be, 75 | modified Documentation, that he communicates to the public or 76 | distributes. 77 | 78 | 3.2. The Licensee may copy, communicate to the public and distribute 79 | verbatim copies of the Documentation, in any medium, subject to the 80 | requirements specified in section 3.1. 81 | 82 | 3.3. The Licensee may modify the Documentation or any portion thereof 83 | provided that upon modification of the Documentation, the Licensee 84 | shall make the modified Documentation available from a Documentation 85 | Location such that it can be easily located by an original Licensor 86 | once the Licensee communicates to the public or distributes the 87 | modified Documentation under section 3.4, and, where required by 88 | section 4.1, by a recipient of a Product. However, the Licensor shall 89 | not assert his rights under the foregoing proviso unless or until a 90 | Product is distributed. 91 | 92 | 3.4. The Licensee may communicate to the public and distribute the 93 | modified Documentation (thereby in addition to being a Licensee also 94 | becoming a Licensor), always provided that he shall: 95 | 96 | a) comply with section 3.1; 97 | 98 | b) cause the modified Documentation to carry prominent notices stating 99 | that the Licensee has modified the Documentation, with the date and 100 | description of the modifications; 101 | 102 | c) cause the modified Documentation to carry a new Documentation 103 | Location notice if the original Documentation provided for one; 104 | 105 | d) make available the modified Documentation at the same level of 106 | abstraction as that of the Documentation, in the preferred format for 107 | making modifications to it (e.g. the native format of the CAD tool as 108 | applicable), and in the event that format is proprietary, in a format 109 | viewable with a tool licensed under an OSI-approved license if the 110 | proprietary tool can create it; and 111 | 112 | e) license the modified Documentation under the terms and conditions 113 | of this Licence or, where applicable, a later version of this Licence 114 | as may be issued by CERN. 115 | 116 | 3.5. The Licence includes a non-exclusive licence to those patents or 117 | registered designs that are held by, under the control of, or 118 | sub-licensable by the Licensor, to the extent necessary to make use of 119 | the rights granted under this Licence. The scope of this section 3.5 120 | shall be strictly limited to the parts of the Documentation or 121 | modified Documentation created by the Licensor. 122 | 123 | 4. Manufacture and distribution of Products 124 | 125 | 4.1. The Licensee may manufacture or distribute Products always 126 | provided that, where such manufacture or distribution requires a 127 | licence under this Licence the Licensee provides to each recipient of 128 | such Products an easy means of accessing a copy of the Documentation 129 | or modified Documentation, as applicable, as set out in section 3. 130 | 131 | 4.2. The Licensee is invited to inform any Licensor who has indicated 132 | his wish to receive this information about the type, quantity and 133 | dates of production of Products the Licensee has (had) manufactured 134 | 135 | 5. Warranty and liability 136 | 137 | 5.1. DISCLAIMER – The Documentation and any modified Documentation are 138 | provided "as is" and any express or implied warranties, including, but 139 | not limited to, implied warranties of merchantability, of satisfactory 140 | quality, non-infringement of third party rights, and fitness for a 141 | particular purpose or use are disclaimed in respect of the 142 | Documentation, the modified Documentation or any Product. The Licensor 143 | makes no representation that the Documentation, modified 144 | Documentation, or any Product, does or will not infringe any patent, 145 | copyright, trade secret or other proprietary right. The entire risk as 146 | to the use, quality, and performance of a Product shall be with the 147 | Licensee and not the Licensor. This disclaimer of warranty is an 148 | essential part of this Licence and a condition for the grant of any 149 | rights granted under this Licence. The Licensee warrants that it does 150 | not act in a consumer capacity. 151 | 152 | 5.2. LIMITATION OF LIABILITY – The Licensor shall have no liability 153 | for direct, indirect, special, incidental, consequential, exemplary, 154 | punitive or other damages of any character including, without 155 | limitation, procurement of substitute goods or services, loss of use, 156 | data or profits, or business interruption, however caused and on any 157 | theory of contract, warranty, tort (including negligence), product 158 | liability or otherwise, arising in any way in relation to the 159 | Documentation, modified Documentation and/or the use, manufacture or 160 | distribution of a Product, even if advised of the possibility of such 161 | damages, and the Licensee shall hold the Licensor(s) free and harmless 162 | from any liability, costs, damages, fees and expenses, including 163 | claims by third parties, in relation to such use. 164 | 165 | 6. General 166 | 167 | 6.1. Except for the rights explicitly granted hereunder, this Licence 168 | does not imply or represent any transfer or assignment of intellectual 169 | property rights to the Licensee. 170 | 171 | 6.2. The Licensee shall not use or make reference to any of the names 172 | (including acronyms and abbreviations), images, or logos under which 173 | the Licensor is known, save in so far as required to comply with 174 | section 3. Any such permitted use or reference shall be factual and 175 | shall in no event suggest any kind of endorsement by the Licensor or 176 | its personnel of the modified Documentation or any Product, or any 177 | kind of implication by the Licensor or its personnel in the 178 | preparation of the modified Documentation or Product. 179 | 180 | 6.3. CERN may publish updated versions of this Licence which retain 181 | the same general provisions as this version, but differ in detail so 182 | far this is required and reasonable. New versions will be published 183 | with a unique version number. 184 | 185 | 6.4. This Licence shall terminate with immediate effect, upon written 186 | notice and without involvement of a court if the Licensee fails to 187 | comply with any of its terms and conditions, or if the Licensee 188 | initiates legal action against Licensor in relation to this 189 | Licence. Section 5 shall continue to apply. 190 | -------------------------------------------------------------------------------- /hardware/PiGI-Prototype-Board-V1.0-PCB-only.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/hardware/PiGI-Prototype-Board-V1.0-PCB-only.jpg -------------------------------------------------------------------------------- /hardware/PiGI-Prototype-Board-V1.0-on-Pi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/hardware/PiGI-Prototype-Board-V1.0-on-Pi.jpg -------------------------------------------------------------------------------- /hardware/PiGI-Prototype-Board-V1.0-perspective.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/hardware/PiGI-Prototype-Board-V1.0-perspective.jpg -------------------------------------------------------------------------------- /hardware/PiGI-Prototype-Board-V1.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/hardware/PiGI-Prototype-Board-V1.0.jpg -------------------------------------------------------------------------------- /hardware/PiGI-V1.0-bom.txt: -------------------------------------------------------------------------------- 1 | Partlist exported from /home/chrono/eagle/PeIger-Counter/PiGI-V1.0.sch at 5/13/13 7:31 PM 2 | 3 | Part Value Device Package Description 4 | C1 220uF CPOL-EU153CLV-0810 153CLV-0810 POLARIZED CAPACITOR, European symbol 5 | C2 10nF/600 C-EUC1206 C1206 CAPACITOR, European symbol 6 | C3 10nF/600 C-EUC1206 C1206 CAPACITOR, European symbol 7 | C4 1nF C-EUC0805 C0805 CAPACITOR, European symbol 8 | C5 330p C-EUC0805 C0805 CAPACITOR, European symbol 9 | C6 100nF C-EUC0805 C0805 CAPACITOR, European symbol 10 | D1 UF4007 MBRA340T3 SMA Surface Mount Schottky Power Rectifier 11 | D2 1N4148 CGRM4001-G SOD-123_MINI-SMA Molded plasitc,JEDEC SOD-123/Mini SMA 12 | HV+ HV+ SMD5 SMD2,54-5,08 SMD PAD 13 | HV- HV- SMD5 SMD2,54-5,08 SMD PAD 14 | IC1 TLC555QDRQ1 TLC555QDRQ1 SO08 TIMER 15 | L1 15mH 1200LRS 1200LRS 16 | LOGO1 OSHW_LOGO_FILLX0150-NT OSHW_LOGO_FILLX0150-NT OSHW_FILLX150_NOTEXT 17 | PIGI-V1.0 RASPBERRY-PI RASPBERRY-PI RASPBERRY-PI Raspberry Pi Board 18 | R1 100k R-EU_R0805 R0805 RESISTOR, European symbol 19 | R2 1M R-EU_R1206 R1206 RESISTOR, European symbol 20 | R3 4.7M R-EU_R1206 R1206 RESISTOR, European symbol 21 | R4 3.9k R-EU_M0805 M0805 RESISTOR, European symbol 22 | R5 330 R-EU_R0805 R0805 RESISTOR, European symbol 23 | R6 220k R-EU_R0805 R0805 RESISTOR, European symbol 24 | R7 1.5k R-EU_R0805 R0805 RESISTOR, European symbol 25 | R8 100k R-EU_R0805 R0805 RESISTOR, European symbol 26 | R9 27k R-EU_R0805 R0805 RESISTOR, European symbol 27 | R10 100 TRIM_EU-RJ9W RJ9W POTENTIOMETER 28 | T1 STN0214 -NPN-SOT223 SOT223 NPN Transistror 29 | T2 2N4401 NPN-TRANSISTOR_SOT23 SOT23 NPN TRANSISTOR 30 | T3 2N4401 NPN-TRANSISTOR_SOT23 SOT23 NPN TRANSISTOR 31 | -------------------------------------------------------------------------------- /software/README.md: -------------------------------------------------------------------------------- 1 | # pyGI/webGI - A software suite for the PiGI 2 | 3 | Python/HTML5/CSS/JS/Websocket software suite for collecting, distributing, 4 | monitoring, mapping and analyzing ionizing radiation counts detected by the PiGI-Module. 5 | 6 | ## Sneak-Preview: Development screenshots in action 7 | 8 | ### Main instrument panel 9 | ![Image](https://apollo.open-resource.org/_media/lab:webgi-mainpanel.jpg) 10 | ### History instrument panel 11 | ![Image](https://apollo.open-resource.org/_media/lab:webgi-historypanel.jpg) 12 | ### Ion trace visualizer 13 | ![Image](https://apollo.open-resource.org/_media/lab:webgi-tracevisualizer.jpg) 14 | 15 | ## Installation 16 | 17 | ### Dependencies 18 | 19 | We've tried to keep external dependencies to a minimum to make it easily 20 | deployable on any flavor of open-source operating system. If you deploy it 21 | successfully on any other OS, please update this: 22 | 23 | #### Currently tested versions 24 | 25 | * greenlet-0.4.2 26 | * bottle-0.12.4 27 | * gevent-1.0 28 | * gevent-websocket-0.9.3 29 | 30 | #### Ubuntu/Raspbian 31 | 32 | $ sudo apt-get install python-pip python-dev libevent-dev 33 | $ sudo pip install ez-setup 34 | $ sudo pip install leveldb greenlet bottle gevent gevent-websocket 35 | 36 | #### Gentoo 37 | 38 | $ emerge -av dev-libs/libevent dev-python/pip 39 | $ pip install ez-setup 40 | $ pip install leveldb greenlet bottle gevent gevent-websocket 41 | 42 | #### Raspberry PI deployment 43 | 44 | If you want to deploy the code on a PI for production in order to count 45 | values from a real GM tube connected to the PiGI-Module, you have to make 46 | sure to satisfy the RPi.GPIO dependency: 47 | 48 | * pip install RPi.GPIO 49 | 50 | This only applies to non-Raspbian installations, since Raspbian ships 51 | RPi.GPIO with the default installation. 52 | 53 | ### Clone repo 54 | 55 | $ git clone https://github.com/apollo-ng/PiGI.git 56 | 57 | ## Configuration 58 | 59 | PyGI checks 3 configuration files, if existent in conf/, updating the 60 | values defined in the file before or using new ones, in the following order: 61 | 62 | * default.cfg (automatically comes shipped per default with examples) 63 | * local.cfg (create this file to override local server settings - gitignored) 64 | * dynamic.cfg (this file will be created automatically, 65 | if the webGI client wants to change server settings - also gitignored) 66 | 67 | When you are deploying on the Pi to count real values and/or want to 68 | change the Web Server/Socket port to 80 rather than 8080 __you have to 69 | run the software as root__. Otherwise the interrupt handling on the Pi 70 | won't work and port 80 will not be accessible due to security (<1024). 71 | 72 | ## Usage 73 | 74 | ### Server Startup 75 | 76 | $ cd PiGI/software/ 77 | $ python pyGIserver.py 78 | 79 | ### Client Access 80 | 81 | Open Browser and goto http://127.0.0.1:8080 82 | 83 | ### License 84 | 85 | This program is free software: you can redistribute it and/or modify 86 | it under the terms of the GNU General Public License as published by 87 | the Free Software Foundation, either version 3 of the License, or 88 | (at your option) any later version. 89 | 90 | This program is distributed in the hope that it will be useful, 91 | but WITHOUT ANY WARRANTY; without even the implied warranty of 92 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 93 | GNU General Public License for more details. 94 | 95 | You should have received a copy of the GNU General Public License 96 | along with this program. If not, see . 97 | 98 | 99 | -------------------------------------------------------------------------------- /software/conf/default.cfg: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Configuration file for pyGI # 3 | ############################### 4 | 5 | # DO NOT edit this file directly, 6 | # copy it to 'local.cfg' 7 | # and override options there 8 | 9 | [node] 10 | name = Geigercounter 11 | lat = 48.00000 12 | lon = 11.00000 13 | alt = 560 14 | opmode = stationary 15 | 16 | [server] 17 | ip = 0.0.0.0 18 | port = 8080 19 | password = undefined 20 | 21 | [logging] 22 | level = INFO 23 | write_file = false 24 | filename = log/pyGI.log 25 | 26 | [db] 27 | path = log/pyGI.db 28 | cache = 8388608 29 | 30 | [geigercounter] 31 | ########################################### 32 | # SIMULATOR Settings (Development/Demo) 33 | ########################################### 34 | 35 | # Set sim_dose_rate in microsievert/h, 36 | # see some examples given below 37 | 38 | # Background / Slightly elevated LDR 39 | sim_dose_rate = 0.27 40 | 41 | # Background / Airplane 42 | #sim_dose_rate = 2.7 43 | 44 | # High radiation area 45 | #sim_dose_rate = 10000 46 | 47 | # Fukushima 48 | #sim_dose_rate = 10000000 49 | 50 | ############################################ 51 | # PiGI & Tube settings (Test/Production) 52 | ############################################ 53 | 54 | # Needs RPi.GPIO python lib and root privileges 55 | 56 | # Geiger counter input pin (single PiGI default) 57 | gpio_port = 4 58 | 59 | # GM tube id/name for reference 60 | # See: https://apollo.open-resource.org/lab:pigi:common-geiger-tube-parameter 61 | tube_id = LND712 62 | 63 | # GM tube specific cpm to microsievert/h conversion factor 64 | tube_rate_factor = 0.00233 65 | 66 | # GM tube specific dead-time in seconds 67 | tube_dead_time = 0.000150 68 | 69 | # GM tube sensitivity, may be "abc", "bc" or "c" 70 | window = abc 71 | 72 | # May be "sim", "env" or "test" 73 | source = test 74 | 75 | 76 | [entropy] 77 | enable = true 78 | filename = log/entropy.bin 79 | -------------------------------------------------------------------------------- /software/examples/c/README.md: -------------------------------------------------------------------------------- 1 | # Software 2 | 3 | This is only examples/prove-of-concept code which is 4 | only left here as an example. The current softfware 5 | implementation is found in ../pigid 6 | 7 | ![Image](software-overview.png) 8 | 9 | This is how it should look like :) 10 | 11 | ## Basic software prove of concept 12 | 13 | This is what is there for now. 14 | 15 | ### Compile 16 | 17 | gcc -o counterd counterd3.c 18 | 19 | ### Setup 20 | 21 | Export the GPIO 22 | 23 | echo 4 > /sys/class/gpio/export 24 | 25 | Set falling edge interrupt detection 26 | 27 | echo falling > /sys/class/gpio/gpio4/edge 28 | 29 | Start the counter 30 | 31 | ./counterd 32 | -------------------------------------------------------------------------------- /software/examples/c/counterd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define GPIO_FN_MAXLEN 32 8 | #define POLL_TIMEOUT 1000 9 | #define RDBUF_LEN 5 10 | #define GPIO_INPUT 4 11 | 12 | int main(int argc, char **argv) { 13 | char fn[GPIO_FN_MAXLEN]; 14 | int fd,ret; 15 | struct pollfd pfd; 16 | char rdbuf[RDBUF_LEN]; 17 | 18 | memset(rdbuf, 0x00, RDBUF_LEN); 19 | memset(fn, 0x00, GPIO_FN_MAXLEN); 20 | 21 | if(strcmp(argv[1],"-h") == 0) { 22 | printf("Usage: GPIO must be exported to sysfs and have enabled edge detection (echo 4 /sys/class/gpio/export; echo falling > /sys/class/gpio4/edge;)\n"); 23 | exit (0); 24 | } 25 | snprintf(fn, GPIO_FN_MAXLEN-1, "/sys/class/gpio/gpio4/value", GPIO_INPUT); 26 | fd=open(fn, O_RDONLY); 27 | if(fd<0) { 28 | perror(fn); 29 | return 2; 30 | } 31 | pfd.fd=fd; 32 | pfd.events=POLLPRI; 33 | 34 | ret=read(fd, rdbuf, RDBUF_LEN-1); 35 | if(ret<0) { 36 | perror("read()"); 37 | return 4; 38 | } 39 | printf("value is: %s\n", rdbuf); 40 | 41 | while(1) { 42 | memset(rdbuf, 0x00, RDBUF_LEN); 43 | lseek(fd, 0, SEEK_SET); 44 | ret=poll(&pfd, 1, POLL_TIMEOUT); 45 | if(ret<0) { 46 | perror("poll()"); 47 | close(fd); 48 | return 3; 49 | } 50 | if(ret==0) { 51 | printf("timeout\n"); 52 | continue; 53 | } 54 | ret=read(fd, rdbuf, RDBUF_LEN-1); 55 | if(ret<0) { 56 | perror("read()"); 57 | return 4; 58 | } 59 | printf("interrupt, value is: %s\n", rdbuf); 60 | } 61 | close(fd); 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /software/examples/c/counterd1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Set GPIO to 4 (Header Pin 7) 8 | 9 | #define GPIO 4 10 | 11 | #define GPIO_FN_MAXLEN 32 12 | #define POLL_TIMEOUT 100 13 | #define RDBUF_LEN 5 14 | 15 | int main(int argc, char **argv) { 16 | 17 | char fn[GPIO_FN_MAXLEN]; 18 | int fd,ret,gcnt; 19 | struct pollfd pfd; 20 | char rdbuf[RDBUF_LEN]; 21 | 22 | memset(rdbuf, 0x00, RDBUF_LEN); 23 | memset(fn, 0x00, GPIO_FN_MAXLEN); 24 | 25 | snprintf(fn, GPIO_FN_MAXLEN-1, "/sys/class/gpio/gpio%d/value", GPIO); 26 | fd=open(fn, O_RDONLY); 27 | 28 | if(fd<0) { 29 | perror(fn); 30 | return 2; 31 | } 32 | 33 | pfd.fd=fd; 34 | pfd.events=POLLPRI; 35 | 36 | ret=read(fd, rdbuf, RDBUF_LEN-1); 37 | 38 | if(ret<0) { 39 | perror("read()"); 40 | return 4; 41 | } 42 | 43 | gcnt=0; 44 | 45 | while(1) { 46 | 47 | memset(rdbuf, 0x00, RDBUF_LEN); 48 | lseek(fd, 0, SEEK_SET); 49 | ret=poll(&pfd, 1, POLL_TIMEOUT); 50 | 51 | if(ret<0) { 52 | perror("poll()"); 53 | close(fd); 54 | return 3; 55 | } 56 | 57 | if(ret==0) { 58 | // printf("timeout\n"); 59 | continue; 60 | } 61 | 62 | ret=read(fd, rdbuf, RDBUF_LEN-1); 63 | 64 | if(ret<0) { 65 | perror("read()"); 66 | return 4; 67 | } 68 | 69 | gcnt++; 70 | printf("INT: %d\n", gcnt); 71 | // printf("ZIPPPPPP"); 72 | } 73 | 74 | close(fd); 75 | return 0; 76 | 77 | } 78 | -------------------------------------------------------------------------------- /software/examples/c/counterd2.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Set GPIO to 4 (Header Pin 7) 10 | 11 | #define GPIO 4 12 | 13 | #define GPIO_FN_MAXLEN 32 14 | #define POLL_TIMEOUT 100 15 | #define RDBUF_LEN 5 16 | 17 | int main(int argc, char **argv) { 18 | 19 | char fn[GPIO_FN_MAXLEN]; 20 | int fd,ret,gcnt; 21 | struct pollfd pfd; 22 | char rdbuf[RDBUF_LEN]; 23 | 24 | unsigned int count = 0; 25 | unsigned int countStartTime = (int)time(NULL); 26 | int countsPerMinute; 27 | 28 | float coefficientOfConversion = 140.0 / 60000.0; 29 | float sV = 0; 30 | 31 | memset(rdbuf, 0x00, RDBUF_LEN); 32 | memset(fn, 0x00, GPIO_FN_MAXLEN); 33 | 34 | snprintf(fn, GPIO_FN_MAXLEN-1, "/sys/class/gpio/gpio%d/value", GPIO); 35 | fd=open(fn, O_RDONLY); 36 | 37 | if(fd<0) { 38 | perror(fn); 39 | return 2; 40 | } 41 | 42 | pfd.fd=fd; 43 | pfd.events=POLLPRI; 44 | 45 | ret=read(fd, rdbuf, RDBUF_LEN-1); 46 | 47 | if(ret<0) { 48 | perror("read()"); 49 | return 4; 50 | } 51 | 52 | gcnt=0; 53 | 54 | while(1) { 55 | 56 | memset(rdbuf, 0x00, RDBUF_LEN); 57 | lseek(fd, 0, SEEK_SET); 58 | ret=poll(&pfd, 1, POLL_TIMEOUT); 59 | 60 | if(ret<0) { 61 | perror("poll()"); 62 | close(fd); 63 | return 3; 64 | } 65 | 66 | if(ret==0) { 67 | time_t ltime; /* calendar time */ 68 | ltime=time(NULL); /* get current cal time */ 69 | //printf("%s",asctime( localtime(<ime) ) ); 70 | 71 | 72 | unsigned int now = (int)time(NULL); 73 | unsigned int elapsedTime = now - countStartTime; 74 | //printf("%d",elapsedTime); //prints timer 75 | if (elapsedTime >= 60) { 76 | countsPerMinute = gcnt; 77 | sV = (float) gcnt * coefficientOfConversion; 78 | printf("| %d cpm | %.2f uSv/h |\n", gcnt, sV); 79 | gcnt = 0; 80 | countStartTime = now; 81 | } 82 | continue; 83 | } 84 | 85 | ret=read(fd, rdbuf, RDBUF_LEN-1); 86 | 87 | if(ret<0) { 88 | perror("read()"); 89 | return 4; 90 | } 91 | 92 | gcnt++; 93 | // printf("."); 94 | printf("INT: %d\n", gcnt); 95 | // printf("ZIPPPPPP"); 96 | } 97 | 98 | close(fd); 99 | return 0; 100 | 101 | } 102 | -------------------------------------------------------------------------------- /software/examples/c/counterd3.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Set GPIO to 4 (Header Pin 7) 10 | 11 | #define GPIO 4 12 | 13 | #define GPIO_FN_MAXLEN 32 14 | #define POLL_TIMEOUT 100 15 | #define RDBUF_LEN 5 16 | 17 | int main(int argc, char **argv) { 18 | 19 | char fn[GPIO_FN_MAXLEN]; 20 | int fd,ret,gcnt; 21 | struct pollfd pfd; 22 | char rdbuf[RDBUF_LEN]; 23 | 24 | unsigned int count = 0; 25 | unsigned int countStartTime = (int)time(NULL); 26 | int countsPerMinute; 27 | 28 | float coefficientOfConversion = 140.0 / 60000.0; 29 | float sV = 0; 30 | float timeFactor = 0; 31 | 32 | memset(rdbuf, 0x00, RDBUF_LEN); 33 | memset(fn, 0x00, GPIO_FN_MAXLEN); 34 | 35 | snprintf(fn, GPIO_FN_MAXLEN-1, "/sys/class/gpio/gpio%d/value", GPIO); 36 | fd=open(fn, O_RDONLY); 37 | 38 | if(fd<0) { 39 | perror(fn); 40 | return 2; 41 | } 42 | 43 | pfd.fd=fd; 44 | pfd.events=POLLPRI; 45 | 46 | ret=read(fd, rdbuf, RDBUF_LEN-1); 47 | 48 | if(ret<0) { 49 | perror("read()"); 50 | return 4; 51 | } 52 | 53 | gcnt=0; 54 | 55 | while(1) { 56 | 57 | memset(rdbuf, 0x00, RDBUF_LEN); 58 | lseek(fd, 0, SEEK_SET); 59 | ret=poll(&pfd, 1, POLL_TIMEOUT); 60 | 61 | if(ret<0) { 62 | perror("poll()"); 63 | close(fd); 64 | return 3; 65 | } 66 | 67 | if(ret==0) { 68 | time_t ltime; /* calendar time */ 69 | ltime=time(NULL); /* get current cal time */ 70 | //printf("%s",asctime( localtime(<ime) ) ); 71 | 72 | 73 | unsigned int now = (int)time(NULL); 74 | unsigned int elapsedTime = now - countStartTime; 75 | 76 | 77 | if (elapsedTime > 59) { 78 | countsPerMinute = gcnt; 79 | sV = (float) gcnt * coefficientOfConversion; 80 | // printf("| %d cpm | %.2f uSv/h |\n", gcnt, sV); 81 | gcnt = 0; 82 | countStartTime = now; 83 | } 84 | else 85 | { 86 | timeFactor = (float) 60/elapsedTime; 87 | sV = (float) gcnt * coefficientOfConversion * timeFactor; 88 | printf("\r| CPM: %d | \033[1;31m%.3f uSv/h\033[0m |", gcnt, sV); 89 | } 90 | 91 | continue; 92 | } 93 | 94 | ret=read(fd, rdbuf, RDBUF_LEN-1); 95 | 96 | if(ret<0) { 97 | perror("read()"); 98 | return 4; 99 | } 100 | 101 | gcnt++; 102 | // printf("."); 103 | // printf("INT: %d\n", gcnt); 104 | // printf("ZIPPPPPP"); 105 | } 106 | 107 | close(fd); 108 | return 0; 109 | 110 | } 111 | -------------------------------------------------------------------------------- /software/examples/c/counterd4.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define DAEMON_NAME "counterd" 10 | 11 | void daemonShutdown(); 12 | void signal_handler(int sig); 13 | void daemonize(char *rundir, char *pidfile); 14 | 15 | int pidFilehandle; 16 | 17 | void signal_handler(int sig) 18 | { 19 | switch(sig) 20 | { 21 | case SIGHUP: 22 | printf("Received SIGHUP"); 23 | break; 24 | case SIGINT: 25 | case SIGTERM: 26 | printf("Received SIGTERM - Daemon exiting"); 27 | daemonShutdown(); 28 | exit(EXIT_SUCCESS); 29 | break; 30 | default: 31 | printf("Unhandled signal %s", strsignal(sig)); 32 | break; 33 | } 34 | } 35 | 36 | void daemonShutdown() 37 | { 38 | close(pidFilehandle); 39 | } 40 | 41 | void daemonize(char *rundir, char *pidfile) 42 | { 43 | int pid, sid, i; 44 | char str[10]; 45 | struct sigaction newSigAction; 46 | sigset_t newSigSet; 47 | 48 | /* Check if parent process id is set */ 49 | if (getppid() == 1) 50 | { 51 | /* PPID exists, therefore we are already a daemon */ 52 | return; 53 | } 54 | 55 | /* Set signal mask - signals we want to block */ 56 | sigemptyset(&newSigSet); 57 | sigaddset(&newSigSet, SIGCHLD); /* ignore child - i.e. we don't need to wait for it */ 58 | sigaddset(&newSigSet, SIGTSTP); /* ignore TTY stop signals */ 59 | sigaddset(&newSigSet, SIGTTOU); /* ignore TTY background writes */ 60 | sigaddset(&newSigSet, SIGTTIN); /* ignore TTY background reads */ 61 | sigprocmask(SIG_BLOCK, &newSigSet, NULL); /* Block the above specified signals */ 62 | 63 | /* Set up a signal handler */ 64 | newSigAction.sa_handler = signal_handler; 65 | sigemptyset(&newSigAction.sa_mask); 66 | newSigAction.sa_flags = 0; 67 | 68 | /* Signals to handle */ 69 | sigaction(SIGHUP, &newSigAction, NULL); /* catch hangup signal */ 70 | sigaction(SIGTERM, &newSigAction, NULL); /* catch term signal */ 71 | sigaction(SIGINT, &newSigAction, NULL); /* catch interrupt signal */ 72 | 73 | /* Fork*/ 74 | pid = fork(); 75 | 76 | if (pid < 0) 77 | { 78 | /* Could not fork */ 79 | exit(EXIT_FAILURE); 80 | } 81 | 82 | if (pid > 0) 83 | { 84 | /* Child created ok, so exit parent process */ 85 | printf("Child process created: %d\n", pid); 86 | exit(EXIT_SUCCESS); 87 | } 88 | 89 | /* Child continues */ 90 | 91 | umask(027); /* Set file permissions 750 */ 92 | 93 | /* Get a new process group */ 94 | sid = setsid(); 95 | 96 | if (sid < 0) 97 | { 98 | exit(EXIT_FAILURE); 99 | } 100 | 101 | /* close all descriptors */ 102 | for (i = getdtablesize(); i >= 0; --i) 103 | { 104 | close(i); 105 | } 106 | 107 | /* Route I/O connections */ 108 | 109 | /* Open STDIN */ 110 | //i = open("/dev/null", O_RDWR); 111 | i = open("/tmp/test.log", O_RDWR); 112 | /* STDOUT */ 113 | dup(i); 114 | 115 | /* STDERR */ 116 | dup(i); 117 | 118 | chdir(rundir); /* change running directory */ 119 | 120 | /* Ensure only one copy */ 121 | pidFilehandle = open(pidfile, O_RDWR|O_CREAT, 0600); 122 | 123 | if (pidFilehandle == -1 ) 124 | { 125 | /* Couldn't open lock file */ 126 | printf("Could not open PID lock file %s, exiting", pidfile); 127 | exit(EXIT_FAILURE); 128 | } 129 | 130 | /* Try to lock file */ 131 | if (lockf(pidFilehandle,F_TLOCK,0) == -1) 132 | { 133 | /* Couldn't get lock on lock file */ 134 | printf("Could not lock PID lock file %s, exiting", pidfile); 135 | exit(EXIT_FAILURE); 136 | } 137 | 138 | /* Get and format PID */ 139 | sprintf(str,"%d\n",getpid()); 140 | 141 | /* write pid to lockfile */ 142 | write(pidFilehandle, str, strlen(str)); 143 | } 144 | 145 | int main() 146 | { 147 | printf("Daemon starting up"); 148 | 149 | /* Deamonize */ 150 | daemonize("/tmp/", "/tmp/daemon.pid"); 151 | 152 | printf("Daemon running"); 153 | 154 | while (1) 155 | { 156 | printf("Daemon says Hello"); 157 | sleep(1); 158 | } 159 | } 160 | 161 | -------------------------------------------------------------------------------- /software/examples/python/entropygeiger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #entropy generating geigercounter 3 | import geiger 4 | import time 5 | import datetime 6 | 7 | OUT_FILE = "entropy.bin" 8 | 9 | class EntropyGeigerCounter(geiger.GeigerCounter): 10 | def __init__(self): 11 | #setup vars for randomness production 12 | self.toggle = False 13 | self.t0 = self.t1 = self.t2 = datetime.datetime.now() 14 | self.bitstring = "" 15 | 16 | #call __init__ of superclass 17 | geiger.GeigerCounter.__init__(self) 18 | 19 | 20 | def tick (self,pin=0): 21 | # This works like this: 22 | # time: |------------|-------------|-----------|-----------| 23 | # tick 0: t0 24 | # tick 1: t0 t1 25 | # tick 2: t2 t1 t0 26 | # d0 d1 27 | # tick 3: t2 t0 t1 28 | # tick 4: t2 t1 t0 29 | # dO d1 30 | 31 | self.tick_counter += 1 32 | if (self.tick_counter % 2) == 0: 33 | self.t2 = self.t0 34 | self.t0 = datetime.datetime.now() 35 | d0 = self.t1 - self.t2 36 | d1 = self.t0 - self.t1 37 | 38 | if d0 > d1: 39 | self.bitstring += "1" if self.toggle else "0" 40 | elif d0 < d1: 41 | self.bitstring += "0" if self.toggle else "1" 42 | else: #d0 = d1 43 | print "Collision" 44 | 45 | self.toggle = not self.toggle 46 | 47 | else: 48 | self.t1 = datetime.datetime.now() 49 | 50 | 51 | def handle_bitstring(self): 52 | with open(OUT_FILE,"ab") as f: 53 | while len(self.bitstring)>=8: 54 | byte_bin = self.bitstring[:8] 55 | self.bitstring = self.bitstring[8:] 56 | byte_int = int(byte_bin,2) 57 | byte_hex = hex(byte_int) 58 | byte_chr = chr(byte_int) 59 | print "%s %3d %4s %s"%(byte_bin,byte_int, 60 | byte_hex,byte_chr) 61 | f.write(byte_chr) 62 | 63 | 64 | if __name__ == "__main__": 65 | egc = EntropyGeigerCounter() 66 | while True: 67 | egc.handle_bitstring() 68 | time.sleep(0.1) 69 | -------------------------------------------------------------------------------- /software/examples/python/geiger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #minimal geigercounter 3 | 4 | import time 5 | import threading 6 | import random 7 | 8 | try: 9 | import RPi.GPIO as GPIO 10 | geiger_simulate = False 11 | except ImportError: 12 | print "Simulating" 13 | geiger_simulate = True 14 | 15 | GPIO_PIGI = 4 16 | SIM_PER_SEC = 100 17 | 18 | class GeigerCounter(): 19 | def __init__(self): 20 | self.tick_counter = 0 21 | 22 | if geiger_simulate: 23 | self.simulator = threading.Thread(target=self.simulate_ticking) 24 | self.simulator.daemon = True 25 | self.simulator.start() 26 | else: 27 | GPIO.setmode(GPIO.BCM) 28 | GPIO.setup(GPIO_PIGI,GPIO.IN) 29 | GPIO.add_event_detect(GPIO_PIGI,GPIO.FALLING) 30 | GPIO.add_event_callback(GPIO_PIGI,self.tick) 31 | 32 | def simulate_ticking(self): 33 | while True: 34 | time.sleep(random.random()/(2*SIM_PER_SEC)) 35 | self.tick() 36 | 37 | def tick (self,pin=0): 38 | self.tick_counter += 1 39 | print "Ticks: %d"%self.tick_counter 40 | 41 | 42 | if __name__ == "__main__": 43 | gc = GeigerCounter() 44 | while True: 45 | time.sleep(1) 46 | -------------------------------------------------------------------------------- /software/init.d/README.txt: -------------------------------------------------------------------------------- 1 | copy the "pigid" file from this directory into "/etc/init.d/" 2 | -------------------------------------------------------------------------------- /software/init.d/pyGIserver: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: pyGIserver 5 | # Required-Start: $remote_fs $syslog 6 | # Required-Stop: $remote_fs $syslog 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: raspberry pi geiger-counter interface daemon 10 | # Description: raspberry pi geiger-counter interface daemon 11 | ### END INIT INFO 12 | 13 | # Change the next 3 lines to suit where you install your script and what you want to call it 14 | DIR=/opt/PiGI/software 15 | DAEMON=$DIR/pyGIserver.py 16 | DAEMON_NAME=pyGIserver 17 | 18 | # This next line determines what user the script runs as. 19 | # Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python. 20 | DAEMON_USER=root 21 | 22 | # The process ID of the script when it runs is stored here: 23 | PIDFILE=/var/run/$DAEMON_NAME.pid 24 | 25 | . /lib/lsb/init-functions 26 | 27 | do_start () { 28 | log_daemon_msg "Starting $DAEMON_NAME daemon" 29 | start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON 30 | log_end_msg $? 31 | } 32 | do_stop () { 33 | log_daemon_msg "Stopping $DAEMON_NAME daemon" 34 | start-stop-daemon --stop --pidfile $PIDFILE --retry 10 35 | log_end_msg $? 36 | } 37 | 38 | case "$1" in 39 | 40 | start|stop) 41 | do_${1} 42 | ;; 43 | 44 | restart|reload|force-reload) 45 | do_stop 46 | do_start 47 | ;; 48 | 49 | status) 50 | status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $? 51 | ;; 52 | *) 53 | echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}" 54 | exit 1 55 | ;; 56 | 57 | esac 58 | exit 0 59 | -------------------------------------------------------------------------------- /software/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/log/.keep -------------------------------------------------------------------------------- /software/public/assets/font/arimo-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/arimo-regular.eot -------------------------------------------------------------------------------- /software/public/assets/font/arimo-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/arimo-regular.ttf -------------------------------------------------------------------------------- /software/public/assets/font/arimo-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/arimo-regular.woff -------------------------------------------------------------------------------- /software/public/assets/font/digital-7-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/digital-7-webfont.eot -------------------------------------------------------------------------------- /software/public/assets/font/digital-7-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/digital-7-webfont.ttf -------------------------------------------------------------------------------- /software/public/assets/font/digital-7-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/digital-7-webfont.woff -------------------------------------------------------------------------------- /software/public/assets/font/digital-7.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/digital-7.eot -------------------------------------------------------------------------------- /software/public/assets/font/digital-7.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/digital-7.ttf -------------------------------------------------------------------------------- /software/public/assets/font/digital-7.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/digital-7.woff -------------------------------------------------------------------------------- /software/public/assets/font/digital-7_mono.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/digital-7_mono.eot -------------------------------------------------------------------------------- /software/public/assets/font/digital-7_mono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/digital-7_mono.ttf -------------------------------------------------------------------------------- /software/public/assets/font/digital-7_mono.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/digital-7_mono.woff -------------------------------------------------------------------------------- /software/public/assets/font/digital-7_mono_italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/digital-7_mono_italic.eot -------------------------------------------------------------------------------- /software/public/assets/font/digital-7_mono_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/digital-7_mono_italic.ttf -------------------------------------------------------------------------------- /software/public/assets/font/digital-7_mono_italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/digital-7_mono_italic.woff -------------------------------------------------------------------------------- /software/public/assets/font/sourcesanspro-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/sourcesanspro-regular.eot -------------------------------------------------------------------------------- /software/public/assets/font/sourcesanspro-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/sourcesanspro-regular.ttf -------------------------------------------------------------------------------- /software/public/assets/font/sourcesanspro-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/sourcesanspro-regular.woff -------------------------------------------------------------------------------- /software/public/assets/font/titillium-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/titillium-regular.eot -------------------------------------------------------------------------------- /software/public/assets/font/titillium-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/titillium-regular.ttf -------------------------------------------------------------------------------- /software/public/assets/font/titillium-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/titillium-regular.woff -------------------------------------------------------------------------------- /software/public/assets/font/webgi.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/webgi.eot -------------------------------------------------------------------------------- /software/public/assets/font/webgi.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright (C) 2014 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /software/public/assets/font/webgi.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/webgi.ttf -------------------------------------------------------------------------------- /software/public/assets/font/webgi.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/font/webgi.woff -------------------------------------------------------------------------------- /software/public/assets/img/apollo-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/img/apollo-badge.png -------------------------------------------------------------------------------- /software/public/assets/img/grips.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /software/public/assets/img/orm_bw.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 57 | 59 | 69 | 79 | 89 | 99 | 109 | 119 | 129 | OPEN RADIATION MONITORING 138 | SCIENTIA PRAESIDIUM OMNI EST 147 | 152 | 162 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /software/public/assets/img/orm_col.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 41 | 45 | 49 | 50 | 52 | 53 | 55 | image/svg+xml 56 | 58 | 59 | 60 | 61 | 62 | 66 | 68 | 78 | 88 | 98 | 101 | 111 | 121 | 131 | 141 | 151 | 161 | 162 | OPEN RADIATION MONITORING 174 | SCIENTIA PRAESIDIUM OMNI EST 183 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /software/public/assets/img/orm_new.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 23 | 27 | 31 | 32 | 41 | 46 | 55 | 57 | 61 | 65 | 66 | 72 | 81 | 83 | 87 | 91 | 92 | 101 | 110 | 112 | 116 | 120 | 121 | 127 | 136 | 138 | 142 | 146 | 147 | 156 | 165 | 167 | 171 | 175 | 176 | 182 | 191 | 193 | 197 | 201 | 202 | 211 | 220 | 222 | 226 | 230 | 231 | 240 | 249 | 250 | 274 | 278 | 282 | 286 | 290 | 291 | 293 | 294 | 296 | image/svg+xml 297 | 299 | 300 | 301 | 302 | 303 | 308 | 318 | 328 | 338 | 348 | 358 | 368 | 378 | 388 | 396 | 406 | 416 | OPEN RADIATION MONITORING 426 | SCIENTIA PRAESIDIUM OMNI EST 438 | 439 | 440 | -------------------------------------------------------------------------------- /software/public/assets/img/panel_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/img/panel_bg.png -------------------------------------------------------------------------------- /software/public/assets/js/jqtouch.min.js: -------------------------------------------------------------------------------- 1 | (function(){$.jQTouch=function(t){function e(t){"string"==typeof t.selector&&"string"==typeof t.name&&P.push(t)}function n(t){"string"==typeof t.name&&"function"==typeof t.isSupported&&"function"==typeof t.fn&&O.push(t)}function o(t,e){A.unshift({page:t,animation:e,hash:"#"+t.attr("id"),id:t.attr("id")})}function a(t){var e=$(t.target);e.is(q.join(", "))||(e=$(t.target).closest(q.join(", "))),e&&e.attr("href")&&!e.isExternalLink()&&t.preventDefault(),$.support.touch||$(t.target).trigger("tap",t)}function r(t,e,n,a){function r(){var o=H;$.support.animationEvents&&n&&M.useAnimations?(t.unbind("webkitAnimationEnd",r),t.removeClass(s+" out inmotion"),s&&e.removeClass(s),E.removeClass("animating animating3d"),M.trackScrollPositions===!0&&(e.css("top",-e.data("lastScroll")),setTimeout(function(){e.css("top",0),window.scroll(0,e.data("lastScroll")),$(".scroll",e).each(function(){this.scrollTop=-$(this).data("lastScroll")})},0))):(t.removeClass(s+" out inmotion"),s&&e.removeClass(s),o+=260),setTimeout(function(){e.removeClass("in"),window.scroll(0,0)},o),t.unselect(),e.trigger("pageAnimationEnd",{direction:"in",animation:n,back:a}),t.trigger("pageAnimationEnd",{direction:"out",animation:n,back:a})}if(a=a?a:!1,void 0===e||0===e.length)return $.fn.unselect(),!1;if(e.hasClass("current"))return $.fn.unselect(),!1;if($(":focus").trigger("blur"),t.trigger("pageAnimationStart",{direction:"out",back:a}),e.trigger("pageAnimationStart",{direction:"in",back:a}),$.support.animationEvents&&n&&M.useAnimations){!$.support.transform3d&&n.is3d&&(n.name=M.defaultAnimation);var s=n.name,u=n.is3d?"animating3d":"";a&&(s=s.replace(/left|right|up|down|in|out/,i)),t.bind("webkitAnimationEnd",r),E.addClass("animating "+u);var c=window.pageYOffset;M.trackScrollPositions===!0&&e.css("top",window.pageYOffset-(e.data("lastScroll")||0)),e.addClass(s+" in current"),t.removeClass("current").addClass(s+" out inmotion"),M.trackScrollPositions===!0&&(t.data("lastScroll",c),$(".scroll",t).each(function(){$(this).data("lastScroll",this.scrollTop)}))}else e.addClass("current in"),t.removeClass("current"),r();return Y=e,a?A.shift():o(Y,n),g(Y.attr("id")),!0}function i(t){var e={up:"down",down:"up",left:"right",right:"left","in":"out",out:"in"};return e[t]||t}function s(){return D}function u(){1>A.length,1===A.length&&window.history.go(-1);var t=A[0],e=A[1];return r(t.page,e.page,t.animation,!0)?X:!1}function c(t,e){var n=A[0].page;if("string"==typeof e)for(var o=0,a=P.length;a>o;o++)if(P[o].name===e){e=P[o];break}if("string"==typeof t){var i=$(t);if(1>i.length)return b(t,{animation:e}),void 0;t=i}return r(n,t,e)?X:!1}function l(){return location.hash===A[0].hash?!0:""===location.hash?(u(),!0):A[1]&&location.hash===A[1].hash?(u(),!0):(c($(location.hash),M.defaultAnimation),void 0)}function d(){if(M.preloadImages)for(var t=M.preloadImages.length-1;t>=0;t--)(new Image).src=M.preloadImages[t];var e=M.addGlossToIcon?"":"-precomposed";M.icon&&(B+=''),M.icon4&&(B+=''),M.startupScreen&&(B+=''),M.fixedViewport&&(B+=''),M.fullScreen&&(B+='',M.statusBar&&(B+='')),B&&x.prepend(B)}function p(){$.fn.isExternalLink=function(){var t=$(this);return"_blank"===t.attr("target")||"external"===t.attr("rel")||t.is('a[href^="http://maps.google.com"], a[href^="mailto:"], a[href^="tel:"], a[href^="javascript:"], a[href*="youtube.com/v"], a[href*="youtube.com/watch"]')},$.fn.makeActive=function(){return $(this).addClass("active")},$.fn.unselect=function(t){t?t.removeClass("active"):$(".active").removeClass("active")}}function f(t){for(var e,n=0,o=P.length;o>n;n++)if(t.is(P[n].selector)){e=P[n];break}return e||(e=M.defaultAnimation),e}function h(t,e){var n=null,o=document.createElement("div");return o.innerHTML=t,$(o).children().each(function(){var t=$(this);t.attr("id")||t.attr("id","page-"+ ++Q),$("#"+t.attr("id")).remove(),E.append(t),E.trigger("pageInserted",{page:t}),(t.hasClass("current")||!n)&&(n=t)}),null!==n?(c(n,e),n):!1}function m(){scrollTo(0,0),D=90===Math.abs(window.orientation)?"landscape":"portrait",E.removeClass("portrait landscape").addClass(D).trigger("turn",{orientation:D})}function g(t){M.updateHash&&(location.hash="#"+t.replace(/^#/,""))}function v(){$.support||($.support={}),$.support.animationEvents=window.WebKitAnimationEvent!==void 0,$.support.touch=window.TouchEvent!==void 0&&window.navigator.userAgent.indexOf("Mobile")>-1&&M.useFastTouch,$.support.transform3d=S(),$.support.ios5=C(),!$.support.touch,!$.support.transform3d;for(var t=0,r=I.length;r>t;t++){var i=I[t];$.isFunction(i)&&$.extend(X,i(X))}for(var s=0,u=L.length;u>s;s++)n(L[s]);j();for(var d=0,p=G.animations.length;p>d;d++){var f=G.animations[d];void 0!==M[f.name+"Selector"]&&(f.selector=M[f.name+"Selector"]),e(f)}q.push(M.touchSelector),q.push(M.backSelector),q.push(M.submitSelector),$(q.join(", ")).css("-webkit-touch-callout","none"),E=$("#jqt");var h=[];0===E.length&&(E=$(document.body).attr("id","jqt")),$.support.transform3d&&h.push("supports3d"),M.useTouchScroll&&($.support.ios5?h.push("touchscroll"):h.push("autoscroll")),M.fullScreenClass&&window.navigator.standalone===!0&&h.push(M.fullScreenClass,M.statusBar),E.addClass(h.join(" ")).bind("click",a).bind("orientationchange",m).bind("submit",w).bind("tap",k).bind($.support.touch?"touchstart":"mousedown",y).trigger("orientationchange"),$(window).bind("hashchange",l);var v=location.hash;Y=0===$("#jqt > .current").length?$("#jqt > *:first-child").addClass("current"):$("#jqt > .current"),g(Y.attr("id")),o(Y),1===$(v).length&&c(v)}function b(t,e){var n={data:null,method:"GET",animation:null,callback:null,$referrer:null},o=$.extend({},n,e);"#"!==t?$.ajax({url:t,data:o.data,type:o.method,success:function(t){var e=h(t,o.animation);e&&("GET"===o.method&&M.cacheGetRequests===!0&&o.$referrer&&o.$referrer.attr("href","#"+e.attr("id")),o.callback&&o.callback(!0))},error:function(){o.$referrer&&o.$referrer.unselect(),o.callback&&o.callback(!1)}}):o.$referrer&&o.$referrer.unselect()}function w(t,e){$(":focus").trigger("blur"),t.preventDefault();var n="string"==typeof t?$(t).eq(0):t.target?$(t.target):$(t);return n.length&&n.is(M.formSelector)&&n.attr("action")?(b(n.attr("action"),{data:n.serialize(),method:n.attr("method")||"POST",animation:f(n),callback:e}),!1):!0}function T(t){var e=t.closest("form");return 0!==e.length?(e.trigger("submit"),!1):!0}function S(){var t,e,n,o,a;return t=document.getElementsByTagName("head")[0],e=document.body,n=document.createElement("style"),n.textContent="@media (transform-3d),(-o-transform-3d),(-moz-transform-3d),(-webkit-transform-3d){#jqt-3dtest{height:3px}}",o=document.createElement("div"),o.id="jqt-3dtest",t.appendChild(n),e.appendChild(o),a=3===o.offsetHeight,n.parentNode.removeChild(n),o.parentNode.removeChild(o),a}function C(){var t=!1,e=/OS (\d+)(_\d+)* like Mac OS X/i,n=window.navigator.userAgent;return e.test(n)&&(t=e.exec(n)[1]>=5),t}function y(t){var e=$(t.target),n=q.join(", ");e.is(n)||(e=e.closest(n)),e.length&&e.attr("href")&&e.addClass("active"),e.on($.support.touch?"touchmove":"mousemove",function(){e.removeClass("active")}),e.on("touchend",function(){e.unbind("touchmove mousemove")})}function k(t){if(t.isDefaultPrevented())return!0;var e=$(t.target);if(e.is(q.join(", "))||(e=e.closest(q.join(", "))),!e.length||!e.attr("href"))return!0;for(var n=e.attr("target"),o=e.prop("hash"),a=e.attr("href"),r={e:t,$el:e,target:n,hash:o,href:a,jQTSettings:M},i=0,s=O.length;s>i;i++){var u=O[i],c=u.isSupported(t,r);if(c){var l=u.fn(t,r);return l}}}function j(){n({name:"external-link",isSupported:function(t,e){return e.$el.isExternalLink()},fn:function(t,e){return e.$el.unselect(),!0}}),n({name:"back-selector",isSupported:function(t,e){return e.$el.is(e.jQTSettings.backSelector)},fn:function(t,e){u(e.hash)}}),n({name:"submit-selector",isSupported:function(t,e){return e.$el.is(e.jQTSettings.submitSelector)},fn:function(t,e){T(e.$el)}}),n({name:"webapp",isSupported:function(t,e){return"_webapp"===e.target},fn:function(t,e){return window.location=e.href,!1}}),n({name:"no-op",isSupported:function(t,e){return"#"===e.href},fn:function(t,e){return e.$el.unselect(),!0}}),n({name:"standard",isSupported:function(t,e){return e.hash&&"#"!==e.hash},fn:function(t,e){var n=f(e.$el);return e.$el.addClass("active"),c($(e.hash).data("referrer",e.$el),n,e.$el.hasClass("reverse")),!1}}),n({name:"external",isSupported:function(){return!0},fn:function(t,e){var n=f(e.$el);return e.$el.addClass("loading active"),b(e.$el.attr("href"),{animation:n,callback:function(){e.$el.removeClass("loading"),setTimeout($.fn.unselect,250,e.$el)},$referrer:e.$el}),!1}})}var E,x=$("head"),A=[],Q=0,M={},Y="",D="portrait",q=[],X={},H=100,I=$.jQTouch.prototype.extensions,L=$.jQTouch.prototype.tapHandlers,O=[],P=[],B="",G={addGlossToIcon:!0,backSelector:".back, .cancel, .goback",cacheGetRequests:!0,debug:!0,defaultAnimation:"slideleft",fixedViewport:!0,formSelector:"form",fullScreen:!0,fullScreenClass:"fullscreen",icon:null,icon4:null,preloadImages:!1,starter:$(document).ready,startupScreen:null,statusBar:"default",submitSelector:".submit",touchSelector:"a, .touch",trackScrollPositions:!0,updateHash:!0,useAnimations:!0,useFastTouch:!0,useTouchScroll:!0,animations:[{name:"cubeleft",selector:".cubeleft, .cube",is3d:!0},{name:"cuberight",selector:".cuberight",is3d:!0},{name:"dissolve",selector:".dissolve"},{name:"fade",selector:".fade"},{name:"flipleft",selector:".flipleft, .flip",is3d:!0},{name:"flipright",selector:".flipright",is3d:!0},{name:"pop",selector:".pop",is3d:!0},{name:"swapleft",selector:".swapleft, .swap",is3d:!0},{name:"swapright",selector:".swapright",is3d:!0},{name:"slidedown",selector:".slidedown"},{name:"slideright",selector:".slideright"},{name:"slideup",selector:".slideup"},{name:"slideleft",selector:".slideleft, .slide, #jqt > * > ul li a"}]};return M=$.extend({},G,t),d(t),p(),X={addAnimation:e,animations:P,getOrientation:s,goBack:u,insertPages:h,goTo:c,history:A,settings:M,submitForm:w},M.starter(v),X},$.jQTouch.prototype.extensions=[],$.jQTouch.prototype.tapHandlers=[],$.jQTouch.addExtension=function(t){$.jQTouch.prototype.extensions.push(t)},$.jQTouch.addTapHandler=function(t){$.jQTouch.prototype.tapHandlers.push(t)}})(); -------------------------------------------------------------------------------- /software/public/assets/js/webGI.js: -------------------------------------------------------------------------------- 1 | if (typeof webGI === 'undefined') { 2 | webGI = {}; 3 | } 4 | 5 | if (typeof getConfig() === 'undefined') { 6 | 7 | webGI.conf = { 8 | websocket_host: "ws://" + window.location.hostname + ":" + window.location.port, 9 | bell_snd: new Audio("assets/snd/ui-bell.mp3"), 10 | ui_action: 'tap', 11 | alerts_enabled: 1, 12 | dtc_enabled: 1, 13 | gps_hacc: 0 14 | }; 15 | 16 | } else 17 | { 18 | getConfig(); 19 | } 20 | 21 | webGI.jQT = new $.jQTouch({ 22 | icon: 'jqtouch.png', 23 | statusBar: 'black-translucent', 24 | useFastTouch: true, 25 | preloadImages: [] 26 | }); 27 | 28 | function initUI() { 29 | $('#cnf_ws_url').val("ws://" + window.location.hostname + ":" + window.location.port); 30 | 31 | // Bind UI events 32 | 33 | // livechart (15m/60m/24h) 34 | $('.live-control').bind(webGI.conf.ui_action, function(event) { 35 | 36 | $('#toggleGauge,#toggleTrace').removeClass('enabled'); 37 | $('.live-control').removeClass('enabled'); 38 | $(this).addClass('enabled'); 39 | 40 | webGI.status.disable(); 41 | webGI.tracer.disable(); 42 | webGI.livechart.enable(); 43 | updateLayout(); 44 | webGI.livechart.set_age(parseInt($(this).attr("data-seconds"))); 45 | 46 | }); 47 | 48 | $('#lvl_val, #lvl_unit').bind(webGI.conf.ui_action, function() { 49 | webGI.status.show_radcon(); 50 | }); 51 | 52 | // CPS/CPM Toggle 53 | $('#count_val, #count_unit').bind(webGI.conf.ui_action, function() { 54 | webGI.status.toggle_counter_unit(); 55 | }); 56 | 57 | $('#userGeoStatus').bind(webGI.conf.ui_action, function() { 58 | webGI.geo.toggle(); 59 | }); 60 | 61 | $('#showModalDateRanger').bind(webGI.conf.ui_action, function() { 62 | $('#modalDateRanger').addClass('md-show'); 63 | }); 64 | 65 | $('#showModalAuth').bind(webGI.conf.ui_action, function() { 66 | $('#modalAuth').addClass('md-show'); 67 | }); 68 | 69 | $('#showModalResetEntropy').bind(webGI.conf.ui_action, function() { 70 | $('#modalResetEntropy').addClass('md-show'); 71 | }); 72 | 73 | $('#showModalResetSettings').bind(webGI.conf.ui_action, function() { 74 | $('#modalResetSettings').addClass('md-show'); 75 | }); 76 | 77 | $('#annotationSave').bind(webGI.conf.ui_action, function() { 78 | webGI.livechart.save_annotation(); 79 | }); 80 | 81 | $('#toggleGauge').bind(webGI.conf.ui_action, function() { 82 | $('#toggleTrace').hide(); //FIXME This is bogus??? 83 | 84 | webGI.tracer.disable(); 85 | webGI.livechart.disable(); 86 | webGI.status.enable(); 87 | updateLayout(); 88 | 89 | $('#toggleGauge').addClass('enabled'); 90 | $('.live-control, #toggleTrace').removeClass('enabled'); 91 | }); 92 | 93 | $('#toggleTrace').bind(webGI.conf.ui_action, function() { 94 | webGI.livechart.disable(); 95 | webGI.status.disable(); 96 | webGI.tracer.enable(); 97 | updateLayout(); 98 | 99 | $('#toggleTrace').addClass('enabled'); 100 | $('.live-control, #toggleGauge').removeClass('enabled'); 101 | }); 102 | 103 | // Audio 104 | $('#toggleAudio').bind(webGI.conf.ui_action, function() { 105 | if (webGI.ticker.enabled) { 106 | $('#toggleAudio').removeClass('enabled'); 107 | $('#toggleAudio span:first-child').removeClass('icon-tick-on'); 108 | $('#toggleAudio span:first-child').addClass('icon-tick-off'); 109 | webGI.ticker.disable(); 110 | } else { 111 | $('#toggleAudio').addClass('enabled'); 112 | $('#toggleAudio span:first-child').removeClass('icon-tick-off'); 113 | $('#toggleAudio span:first-child').addClass('icon-tick-on'); 114 | webGI.ticker.enable(); 115 | } 116 | }); 117 | 118 | $('#toggleLogScale').bind(webGI.conf.ui_action, function() { 119 | if (!webGI.history.log_scale) { 120 | webGI.history.set_log_scale(true); 121 | $('#toggleLogScale').addClass('enabled'); 122 | } else { 123 | webGI.history.set_log_scale(false); 124 | $('#toggleLogScale').removeClass('enabled'); 125 | } 126 | }); 127 | 128 | // Page animation callback events 129 | $('#jqt').bind('pageAnimationStart', function(e, info) { 130 | //console.log('Page animation started'); 131 | }); 132 | 133 | // Orientation change callback event 134 | $('#jqt').bind('turn', function(e, data) { 135 | alert('Orientation changed to: ' + data.orientation); 136 | $('#jqt').removeClass('portrait'); 137 | $('#jqt').addClass('landscape'); 138 | updateLayout(); 139 | }); 140 | 141 | // First working swipe handler test harness. Let's see how we like it 142 | $('#mainPanel').swipeLeft(function() { 143 | webGI.jQT.goTo('#historyPanel', 'slideleft'); 144 | }); 145 | 146 | $('#mainPanel').swipeRight(function() { 147 | webGI.jQT.goTo('#optionsPanel', 'slideright'); 148 | }); 149 | 150 | $('#optionsPanel').swipeLeft(function() { 151 | webGI.jQT.goTo('#mainPanel', 'slideleft'); 152 | }); 153 | 154 | $('#geoSnapshot').bind(webGI.conf.ui_action, function() { 155 | webGI.geo.getCurrentPosition(webGI.options.geoSnapshotCallback); 156 | }); 157 | 158 | $('#saveServerSettings').bind(webGI.conf.ui_action, function() { 159 | webGI.options.save(); 160 | }); 161 | 162 | $('#reloadSettings').bind(webGI.conf.ui_action, function() { 163 | webGI.options.request(); 164 | }); 165 | 166 | $('#resetSettings').bind(webGI.conf.ui_action, function() { 167 | webGI.options.reset(); 168 | }); 169 | 170 | $('#startEntropyDownload').bind(webGI.conf.ui_action, function() { 171 | webGI.options.startEntropyDownload(); 172 | }); 173 | 174 | $('#resetEntropy').bind(webGI.conf.ui_action, function() { 175 | webGI.options.resetEntropy(); 176 | }); 177 | 178 | 179 | $('#simRanger').bind('input', function() { 180 | var val = webGI.options.lin2log(this.value); 181 | 182 | if (val >= 10) { 183 | val = val.toFixed(0); 184 | $('#server_cnf_sim_dose_rate').css({ 185 | "color": "#F5C43C" 186 | }); 187 | } else { 188 | val = val.toFixed(2); 189 | $('#server_cnf_sim_dose_rate').css({ 190 | "color": "#75890c" 191 | }); 192 | } 193 | 194 | //$('#server_cnf_sim_dose_rate').val(val); 195 | document.getElementById("server_cnf_sim_dose_rate").value = val; 196 | }); 197 | 198 | $('#server_cnf_sim_dose_rate').bind('input', function() { 199 | //$('#simRanger').val(webGI.options.log2lin(this.valueAsNumber)); 200 | document.getElementById("simRanger").value = webGI.options.log2lin(this.valueAsNumber); 201 | var val = webGI.options.lin2log(this.valueAsNumber); 202 | 203 | if (val >= 10) { 204 | $('#server_cnf_sim_dose_rate').css({ 205 | "color": "#F5C43C" 206 | }); 207 | } else { 208 | $('#server_cnf_sim_dose_rate').css({ 209 | "color": "#75890c" 210 | }); 211 | } 212 | }); 213 | 214 | /* 215 | $('#jqt').bind('pageAnimationEnd', function(e, info) 216 | { 217 | console.log('Page animation finished'); 218 | updateLayout(); 219 | });*/ 220 | } 221 | 222 | function showErrorModal(title, msg, action) { 223 | //$('#body').find('.md-modal').removeClass('md-show'); 224 | $('.md-show').removeClass('md-show'); 225 | webGI.conf.bell_snd.play(); 226 | 227 | setTimeout(function() { 228 | document.getElementById("modalErrorTitle").innerHTML = title; 229 | //$('#modalErrorTitle').html(title); 230 | document.getElementById("modalErrorMsg").innerHTML = msg; 231 | //$('#modalErrorMsg').html(msg); 232 | 233 | var buttons = 'Close'; 234 | 235 | if (action) { 236 | buttons = buttons + action; 237 | } 238 | 239 | //$('#modalErrorAction').html(buttons); 240 | document.getElementById("modalErrorAction").innerHTML = buttons; 241 | $('#modalError').addClass('md-show'); 242 | }, 243 | 500); 244 | } 245 | 246 | function popModal(type, title, msg, action) { 247 | //$('#body').find('.md-modal').removeClass('md-show'); 248 | $('.md-show').removeClass('md-show'); 249 | webGI.conf.bell_snd.play(); 250 | 251 | setTimeout(function() { 252 | document.getElementById("modalErrorTitle").innerHTML = title; 253 | //$('#modalErrorTitle').html(title); 254 | document.getElementById("modalErrorMsg").innerHTML = msg; 255 | //$('#modalErrorMsg').html(msg); 256 | 257 | var buttons = 'Ack'; 258 | 259 | if (action) { 260 | buttons = buttons + action; 261 | } 262 | 263 | //$('#modalErrorAction').html(buttons); 264 | document.getElementById("modalErrorAction").innerHTML = buttons; 265 | $('#modalError').addClass('md-show'); 266 | }, 267 | 200); 268 | } 269 | 270 | function updateLayout() { 271 | // This is called on DOMReady and on resize/rotate 272 | // FIXME: Nasty hack to keep everything in flux state :) 273 | 274 | var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); 275 | var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); 276 | 277 | // Make the modals stack and sticky 278 | $('.modal').css({ 279 | 'top': '80px', 280 | 'left': (w / 2) - ($('#modalAuth').width() / 2) + 'px' 281 | }); 282 | 283 | var h_offset = 137; 284 | var w_offset = 0; 285 | 286 | $('.instrument').css({ 287 | 'height': h - 85 + 'px' 288 | }); 289 | 290 | var new_h = h - h_offset; 291 | var new_w = $('#mainInstrument').width(); 292 | 293 | $('.instrument-container').css({ 294 | 'height': new_h + 'px', 295 | 'width': new_w + 'px' 296 | }).attr('height', new_h).attr('width', new_w); 297 | $('#traceCanvas').css({ 298 | 'height': new_h + 'px', 299 | 'width': new_w + 'px' 300 | }).attr('height', new_h).attr('width', new_w); 301 | 302 | new_w = $('#historyInstrument').width(); 303 | $('#historyContainer').css({ 304 | 'height': new_h + 'px', 305 | 'width': new_w + 'px' 306 | }).attr('height', new_h).attr('width', new_w); 307 | 308 | //ugly, but we seem to need it 309 | webGI.livechart.init(); 310 | webGI.history.init(); 311 | } 312 | 313 | function saveConfig() { 314 | console.log("Writing config to local storage"); 315 | localStorage.setItem('webGI-localconf', JSON.stringify(webGI.conf)); 316 | } 317 | 318 | function getConfig() { 319 | var localConf = localStorage.getItem('webGI-localconf'); 320 | console.log('Getting local webGI.conf: ', JSON.parse(localConf)); 321 | } 322 | 323 | function escapeHTML(str) { 324 | return str.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); 325 | } 326 | 327 | $(document).ready(function() { 328 | 329 | // Make sure we've got a capable browser 330 | if (!("WebSocket" in window)) { 331 | $('

Oh no, you need a modern browser that supports WebSockets. How about Chromium?

').appendTo('#container'); 332 | return; 333 | } 334 | webGI.websocket.init(); 335 | webGI.status.init(); 336 | webGI.geo.init(); 337 | webGI.ticker.init(); 338 | webGI.alert.init(); 339 | // Set callbacks to updateLayout on window resize and url-hash change (panels) 340 | // Should have been replaced by pageAnimationEnd event but doesn't work as well 341 | $(window).resize(updateLayout); 342 | window.onhashchange = updateLayout; 343 | 344 | // Switch UI click/tap event handler action for stupid apple browsers 345 | if ($.support.touch) { 346 | webGI.conf.ui_action = 'tap'; 347 | } else { 348 | webGI.conf.ui_action = 'click'; 349 | } 350 | 351 | initUI(); 352 | 353 | // Give client some time to settle 354 | setTimeout(function() { 355 | $('.splash').addClass('splash-hidden'); 356 | updateLayout(); 357 | }, 1200); 358 | 359 | }); 360 | -------------------------------------------------------------------------------- /software/public/assets/js/webGI_MODULE.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a skeleton template for webGI modules 3 | * Just copy and extend... 4 | */ 5 | 6 | // Create webGI object if neccessary 7 | if (typeof webGI === 'undefined') { 8 | webGI = {}; 9 | } 10 | 11 | // Add module_name to webGI namespace 12 | webGI.module_name = (function($) { 13 | 14 | /*************************************************************************** 15 | * Public attributes *******************************************************/ 16 | 17 | var my = { 18 | public_attribute : 'foo' 19 | }; 20 | 21 | 22 | /*************************************************************************** 23 | * Private attributes ******************************************************/ 24 | 25 | var private_attribute = 'bar'; 26 | 27 | 28 | /*************************************************************************** 29 | * Public functions ********************************************************/ 30 | 31 | my.public_function = function() { 32 | console.log(my.public_attribute); 33 | private_function(); 34 | }; 35 | 36 | 37 | /*************************************************************************** 38 | * Private functions *******************************************************/ 39 | 40 | function private_function() { 41 | console.log(private_attribute); 42 | } 43 | 44 | 45 | return my; // Do not forget to return my, otherwise nothing will work. 46 | }($)); // Pass jq/zepto to the module construction function call 47 | -------------------------------------------------------------------------------- /software/public/assets/js/webGI_alert.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Alert module 3 | */ 4 | 5 | // Create webGI object if neccessary 6 | if (typeof webGI === 'undefined') { 7 | webGI = {}; 8 | } 9 | 10 | // Add alert module to webGI namespace 11 | webGI.alert = (function($) { 12 | 13 | /*************************************************************************** 14 | * Public attributes *******************************************************/ 15 | 16 | var my = { 17 | enabled: true 18 | }; 19 | 20 | 21 | /*************************************************************************** 22 | * Private attributes ******************************************************/ 23 | 24 | var alert_snd = new Audio('assets/snd/ui-bell.mp3'); 25 | var radcon_alert_ack_lvl = 0; 26 | var radcon_alert_last_ts = 0; 27 | 28 | 29 | /*************************************************************************** 30 | * Public functions ********************************************************/ 31 | 32 | my.init = function() { 33 | 34 | // Add Checkboxes to client settings panel 35 | // and set check status according to config 36 | 37 | webGI.options.addOptionCheckbox( 38 | 'client_settings', 39 | 'cnf_alerts_enabled', 40 | 'Radiation Alerts', 41 | my.enabled 42 | ); 43 | 44 | }; 45 | 46 | my.enable = function() { 47 | my.enabled = true; 48 | }; 49 | 50 | my.disable = function() { 51 | my.enabled = false; 52 | }; 53 | 54 | my.ack_new_lvl = function(lvl) { 55 | 56 | radcon_alert_ack_lvl = lvl; 57 | $('#modalError').removeClass('md-show'); 58 | }; 59 | 60 | my.analyze = function(edr, lvl) { 61 | 62 | if(my.enabled === true) { 63 | check_radcon_lvl_change(lvl); 64 | } else { 65 | return; 66 | } 67 | }; 68 | 69 | 70 | /*************************************************************************** 71 | * Private functions *******************************************************/ 72 | 73 | function check_radcon_lvl_change(lvl) { 74 | 75 | var ts = Math.round(new Date().getTime() / 1000); 76 | if (lvl > radcon_alert_ack_lvl && ts - radcon_alert_last_ts > 10) { 77 | radcon_alert_last_ts = ts; 78 | showErrorModal( 79 | 'RADIATION Warning', 80 | '

RADCON level increased to ' + lvl + '

', 81 | 'Acknowledged' 82 | ); 83 | } else if (lvl < radcon_alert_ack_lvl) { 84 | radcon_alert_ack_lvl = lvl; 85 | } 86 | } 87 | 88 | function edr_watchdog(edr) { 89 | 90 | if(edr > (webGI.log.edr_avg_24*1.2)) 91 | { 92 | console.log('EDR Watchdog fired'); 93 | 94 | showErrorModal( 95 | 'RADIATION Warning', 96 | '

Wow, that tube is really cracking and sparkling now...

' 97 | ); 98 | } 99 | } 100 | 101 | 102 | return my; 103 | 104 | }($)); 105 | -------------------------------------------------------------------------------- /software/public/assets/js/webGI_geo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Client enabled geolocation helper module 3 | */ 4 | 5 | //Create webGI object if neccessary 6 | if (typeof webGI === 'undefined') { 7 | webGI = {}; 8 | } 9 | 10 | // Add module to webGI namespace 11 | webGI.geo = (function($) { 12 | 13 | /*************************************************************************** 14 | * Public attributes *******************************************************/ 15 | 16 | var my = {}; 17 | 18 | 19 | /*************************************************************************** 20 | * Private attributes ******************************************************/ 21 | 22 | var lat = 0; 23 | var lon = 0; 24 | var alt = 0; 25 | var acc = 0; 26 | var watcher = null; 27 | var container_status = null; 28 | var container_loc = null; 29 | 30 | 31 | /*************************************************************************** 32 | * Public functions ********************************************************/ 33 | 34 | my.init = function() { 35 | 36 | // Add Checkbox to client settings panel 37 | // and set check status according to config 38 | webGI.options.addOptionCheckbox( 39 | 'client_settings', 40 | 'cnf_gps_hacc', 41 | 'GPS High Accuracy Mode', 42 | (webGI.conf.gps_hacc === 1) ? true : false 43 | ); 44 | 45 | container_status = $('#userGeoStatus'); 46 | container_loc = $('#userGeoLoc'); 47 | }; 48 | 49 | my.toggle = function() { 50 | 51 | if (navigator.geolocation) { 52 | if (watcher) { 53 | navigator.geolocation.clearWatch(watcher); 54 | watcher = null; 55 | container_status.removeClass('init-blinker icon-dot-circled lock-green lock-yellow lock-red'); 56 | container_status.addClass('icon-target-1'); 57 | container_loc.html(''); 58 | } else { 59 | container_status.addClass('init-blinker'); 60 | 61 | watcher = navigator.geolocation.watchPosition( 62 | geoUpdate, 63 | geoError, { 64 | enableHighAccuracy: (webGI.conf.gps_hacc === 1) ? true : false, 65 | timeout: 60, 66 | maximumAge: 5 67 | } 68 | ); 69 | } 70 | } else { 71 | container_loc.html(''); 72 | showErrorModal( 73 | 'Geolocation unavailable', 74 | '

It seems your browser/device does not support geolocation

' 75 | ); 76 | } 77 | }; 78 | 79 | my.getCurrentPosition = function(callback) { 80 | navigator.geolocation.getCurrentPosition(callback); 81 | }; 82 | 83 | 84 | /*************************************************************************** 85 | * Private functions *******************************************************/ 86 | 87 | function geoUpdate(position) { 88 | 89 | container_status.removeClass('init-blinker icon-target-1'); 90 | container_status.addClass('icon-dot-circled'); 91 | 92 | // Update lock circle to indicate GeoLocation accuracy 93 | if (position.coords.accuracy < 10) { 94 | container_status.removeClass('lock-red lock-yellow'); 95 | container_status.addClass('lock-green'); 96 | } else if (position.coords.accuracy < 25) { 97 | container_status.removeClass('lock-red lock-green'); 98 | container_status.addClass('lock-yellow'); 99 | } else { 100 | container_status.removeClass('lock-yellow lock-green'); 101 | container_status.addClass('lock-red'); 102 | } 103 | 104 | lat = position.coords.latitude; 105 | lon = position.coords.longitude; 106 | alt = position.coords.altitude; 107 | acc = position.coords.accuracy; 108 | 109 | container_loc.html( 110 | position.coords.latitude.toString().substr(0, 8) + ' ' + 111 | position.coords.longitude.toString().substr(0, 8) 112 | ); 113 | } 114 | 115 | function geoError(error) { 116 | 117 | var errors = { 118 | 1: 'Permission denied', 119 | 2: 'Position unavailable', 120 | 3: 'Request timeout', 121 | 4: 'Unknown Error' 122 | }; 123 | 124 | container_status.removeClass('init-blinker icon-dot-circled lock-green lock-yellow lock-red'); 125 | container_status.addClass('icon-target-1'); 126 | container_loc.html(''); 127 | 128 | navigator.geolocation.clearWatch(watcher); 129 | 130 | showErrorModal( 131 | 'Geolocation unavailable', 132 | '

Hmmm, unfortunately, I still could not really determine our location. The browser/device told me:

' + errors[error.code] + '

Possible solutions:

  • Turn on your GPS
  • Allow the browser to share geolocation
' 133 | ); 134 | } 135 | 136 | 137 | return my; 138 | 139 | }($)); 140 | -------------------------------------------------------------------------------- /software/public/assets/js/webGI_history.js: -------------------------------------------------------------------------------- 1 | /* 2 | * History Chart/Data module 3 | */ 4 | 5 | //Create webGI object if neccessary 6 | if (typeof webGI === 'undefined') { 7 | webGI = {}; 8 | } 9 | 10 | //Add module to webGI namespace 11 | webGI.history = (function($) { 12 | 13 | /*************************************************************************** 14 | * Public attributes *******************************************************/ 15 | 16 | var my = {}; 17 | my.container_id = "historyContainer"; 18 | my.log_scale = false; 19 | 20 | 21 | /*************************************************************************** 22 | * Private attributes ******************************************************/ 23 | 24 | var container = null; 25 | var chart = null; 26 | var data = []; 27 | var annotations = []; 28 | 29 | 30 | /*************************************************************************** 31 | * Public functions ********************************************************/ 32 | 33 | my.init = function() { 34 | if (data.length === 0) { 35 | return; 36 | } 37 | 38 | var objToString = function(obj) { 39 | var tabjson=[]; 40 | for (var p in obj) { 41 | if (obj.hasOwnProperty(p)) { 42 | tabjson.push('"'+p +'"'+ ':' + obj[p]); 43 | } 44 | } 45 | tabjson.push(); 46 | return '{'+tabjson.join(',')+'}'; 47 | }; 48 | 49 | var clickCallback = function(e,z,pts) { 50 | alert("e: " + objToString(e)); 51 | alert("z: " + objToString(z)); 52 | alert("pts: " + objToString(pts)); 53 | var x = e.offsetX; 54 | var y = e.offsetY; 55 | var dataXY = chart.toDataCoords(x, y); 56 | $('#eventTS').html(new Date(dataXY[0])); 57 | $('#eventText').val("Enter your annotation text here..."); 58 | webGI.livechart.annotation_ts = dataXY[0] / 1000; 59 | $('#eventEDR').html(dataXY[1].toFixed(2)); 60 | $('#modalAnnotation').addClass('md-show'); 61 | }; 62 | 63 | var annotationClickCallback = function(annotation, point) { 64 | webGI.livechart.annotation_ts = annotation.xval / 1000; 65 | $('#eventTS').html(new Date(annotation.xval)); 66 | $('#eventEDR').html(point.yval.toFixed(2)); 67 | $('#eventText').val(escapeHTML(annotation.text)); 68 | $('#modalAnnotation').addClass('md-show'); 69 | }; 70 | 71 | var pts_info = function(e, x, pts, row) { 72 | 73 | var date = new Date(x).toLocaleString().split(" "); 74 | var str = ""; 75 | var yRangeMaxHighlight = 0; 76 | 77 | for (var i = 0; i < chart.numRows(); i++) { 78 | if ( chart.xAxisRange()[0] <= chart.getValue(i, 0) && x >= chart.getValue(i, 0) ) { 79 | yRangeMaxHighlight += chart.getValue(i, 1); 80 | } 81 | } 82 | 83 | str += " accumulated: " + yRangeMaxHighlight.toFixed(2); 84 | return str; 85 | }; 86 | 87 | chart = new Dygraph(my.container_id, data, { 88 | //interactionModel : { 89 | // 'mousedown' : Dygraph.Interaction.startPan, 90 | // 'mousemove' : Dygraph.Interaction.movePan, 91 | // 'mouseup' : Dygraph.Interaction.endPan, 92 | //'click' : clickV3, 93 | //'dblclick' : dblClickV3, 94 | // 'mousewheel' : Dygraph.Interaction.moveZoom 95 | //}, 96 | showRangeSelector: true, 97 | rangeSelectorPlotFillColor: '#677712', 98 | rangeSelectorPlotStrokeColor: '#677712', 99 | title: 'EAR: $$ uSv/h (AVG) - EAD: $$ uSv (Total)', 100 | titleHeight: 35, 101 | rightGap: 15, 102 | fillAlpha: 0.7, 103 | fillGraph: true, 104 | showRoller: false, 105 | valueRange: [0.01, null], 106 | //yRangePad: 10, 107 | drawCallback: function(dygraph, initial) { 108 | var range = dygraph.yAxisRange(); 109 | if (range[0] != 0.01) { 110 | console.log("Fixing range", range); 111 | range[0] = 0.01; 112 | range[1] = null; //range[1]*2; 113 | dygraph.updateOptions({ 114 | valueRange: range 115 | }); 116 | } 117 | }, 118 | //zoomCallback: function(min,max,y) { 119 | // webGI.history.chart.updateOptions({valueRange: [0.01, null]}); 120 | //}, 121 | interactionModel: { 122 | 'touchend': clickCallback, 123 | 'click': clickCallback 124 | 125 | }, 126 | annotationClickHandler: annotationClickCallback, 127 | /* 128 | clickCallback: function(e, x, pts) { 129 | console.log("Click " + pts_info(e,x,pts)); 130 | }, 131 | pointClickCallback: function(e, p) { 132 | console.log("Point Click " + p.name + ": " + p.x); 133 | },*/ 134 | //includeZero: true, 135 | //connectSeparatedPoints: true, 136 | labels: ['time', 'µSv/h', 'µSv/h (15m avg)'], 137 | xlabel: 'time', 138 | xLabelHeight: 25, 139 | colors: ['#677712', 'yellow'], 140 | 'µSv/h': { 141 | fillGraph: true, 142 | stepPlot: true, 143 | }, 144 | 'µSv/h (15m avg)': { 145 | fillGraph: false, 146 | }, 147 | }); 148 | chart.setAnnotations(annotations); 149 | }; 150 | 151 | my.update = function(msg) { 152 | //console.log("HISTORY"); 153 | data = []; 154 | annotations = []; 155 | $.each(msg.log, function(i, v) { 156 | //var v = JSON.parse(v_json); 157 | var ts = new Date(v.timestamp * 1000); 158 | if (isNaN(ts.getTime())) { 159 | return; 160 | } 161 | if (v.annotation !== "") { 162 | annotations.push({ 163 | series: 'µSv/h', 164 | x: v.timestamp * 1000, 165 | shortText: v.annotation[0], 166 | text: v.annotation 167 | }); 168 | } 169 | data.push([ts, v.data.edr, v.data.edr_avg]); 170 | }); 171 | 172 | if (!webGI.history.chart) { 173 | my.init(); 174 | } else { 175 | chart.updateOptions({ 176 | file: webGI.history.data 177 | }); 178 | chart.setAnnotations(annotations); 179 | } 180 | 181 | }; 182 | 183 | my.set_log_scale = function(enabled) { 184 | chart.updateOptions({ 185 | logscale: enabled 186 | }); 187 | my.log_scale = enabled; 188 | }; 189 | 190 | 191 | /*************************************************************************** 192 | * Private functions *******************************************************/ 193 | 194 | 195 | return my; 196 | 197 | }($)); 198 | -------------------------------------------------------------------------------- /software/public/assets/js/webGI_livechart.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Live Chart/Data websocket module 3 | */ 4 | 5 | // Create webGI object if neccessary 6 | if (typeof webGI === 'undefined') { 7 | webGI = {}; 8 | } 9 | 10 | // Add livechart module to webGI namespace 11 | webGI.livechart = (function($) { 12 | 13 | /*************************************************************************** 14 | * Public attributes *******************************************************/ 15 | 16 | var my = { 17 | container_id: 'chartContainer', 18 | chart_age: 60 * 15, 19 | now: Date.now(), 20 | annotation_ts: null 21 | }; 22 | 23 | 24 | /*************************************************************************** 25 | * Private attributes ******************************************************/ 26 | 27 | var container = null; 28 | var chart = null; 29 | var data = []; 30 | var data_hd = []; 31 | var data_ld = []; 32 | var data_ld_all = null; 33 | var desired_range = null; 34 | var animtimer = null; 35 | var annotations = []; 36 | var edr_avg_24h = 0.1; // FIXME this does not belong here 37 | var chart_colors = ['#677712', 'yellow']; 38 | 39 | 40 | /*************************************************************************** 41 | * Public functions ********************************************************/ 42 | 43 | my.init = function() { 44 | 45 | container = $("#" + my.container_id); 46 | 47 | if (data.length === 0) { 48 | return; 49 | } 50 | 51 | var clickCallback = function(e) { 52 | 53 | var x = e.offsetX; 54 | var y = e.offsetY; 55 | var dataXY = chart.toDataCoords(x, y); 56 | $('#eventTS').html(new Date(parseInt(dataXY[0]))); 57 | $('#eventText').val("Enter your annotation text here..."); 58 | my.annotation_ts = dataXY[0] / 1000; 59 | $('#eventEDR').html(dataXY[1].toFixed(2)); 60 | $('#modalAnnotation').addClass('md-show'); 61 | }; 62 | 63 | var annotationClickCallback = function(annotation, point) { 64 | 65 | my.annotation_ts = annotation.xval / 1000; 66 | $('#eventTS').html(new Date(annotation.xval)); 67 | $('#eventEDR').html(point.yval.toFixed(2)); 68 | $('#eventText').val(escapeHTML(annotation.text)); 69 | $('#modalAnnotation').addClass('md-show'); 70 | }; 71 | 72 | my.save_annotation = function() { 73 | 74 | var annotation_text = $('#eventText').val(); 75 | //console.log(my.annotation_ts, annotation_text); 76 | my.pushAnnotation(my.annotation_ts, annotation_text); 77 | my.requestLog(60 * 60 * 24, false); 78 | }; 79 | 80 | chart = new Dygraph(my.container_id, data, { 81 | 82 | dateWindow: [my.now - my.chart_age * 1000, my.now], 83 | title: 'EAR: $$ uSv/h (AVG) - EAD: $$ uSv (Total)', 84 | titleHeight: 35, 85 | fillGraph: true, 86 | rightGap: 20, 87 | fillAlpha: 0.7, 88 | showRoller: false, 89 | rollPeriod: 1, 90 | interactionModel: { 91 | 'click': clickCallback 92 | }, 93 | annotationClickHandler: annotationClickCallback, 94 | //valueRange: [0,null], 95 | includeZero: true, 96 | animatedZooms: true, 97 | labels: ['time', 'µSv/h', 'µSv/h (15m avg)'], 98 | xlabel: 'time', 99 | colors: chart_colors, 100 | 'µSv/h': { 101 | fillGraph: true, 102 | stepPlot: true, 103 | }, 104 | 'µSv/h (15m avg)': { 105 | fillGraph: false, 106 | } 107 | }); 108 | }; 109 | 110 | 111 | my.update = function(msg) { 112 | 113 | var ts = new Date(msg.timestamp * 1000); 114 | data_hd.push([ts, msg.data.edr, msg.data.edr_avg]); 115 | 116 | //FIXME: push ld data less often 117 | data_ld.push([ts, msg.data.edr, msg.data.edr_avg]); 118 | data_ld_all.push(msg); 119 | 120 | var left_end_ld = new Date((msg.timestamp - 60 * 60 * 24) * 1000); 121 | while (data_ld[0][0] < left_end_ld) data_ld.shift(); 122 | while (data_ld_all[0].timestamp < msg.timestamp - 60 * 60 * 24) data_ld_all.shift(); 123 | 124 | var left_end_hd = new Date((msg.timestamp - 60 * 60 * 1) * 1000); 125 | while (data_hd[0][0] < left_end_hd) data_hd.shift(); 126 | 127 | chart.updateOptions({ 128 | file: data, 129 | dateWindow: [my.now - my.chart_age * 1000, my.now] 130 | }); 131 | }; 132 | 133 | my.updateBacklog = function(msg) { 134 | 135 | //console.log("LOGHISTORY"); 136 | if (msg.hd) { 137 | data_hd = []; 138 | $.each(msg.log, function(i, v) { 139 | var ts = new Date(v.timestamp * 1000); 140 | //var ts = v.timestamp*1000 141 | //if (isNaN(ts.getTime())) 142 | //{ 143 | // return; 144 | //} 145 | data_hd.push([ts, v.data.edr, v.data.edr_avg]); 146 | }); 147 | } else { 148 | data_ld_all = msg.log; 149 | data_ld = []; 150 | var edr_avg = 0; 151 | annotations = []; 152 | $.each(msg.log, function(i, v) { 153 | var ts = new Date(v.timestamp * 1000); 154 | //var ts = v.timestamp*1000 155 | //if (isNaN(ts.getTime())) 156 | //{ 157 | // return; 158 | //} 159 | if (v.annotation !== "") { 160 | annotations.push({ 161 | series: 'µSv/h', 162 | x: v.timestamp * 1000, 163 | shortText: v.annotation[0], 164 | text: v.annotation 165 | }); 166 | } 167 | data_ld.push([ts, v.data.edr, v.data.edr_avg]); 168 | edr_avg += v.data.edr; 169 | 170 | }); 171 | 172 | // Update rolling 24h average EDR for alert reference 173 | edr_avg_24 = (edr_avg / msg.log.length); 174 | } 175 | 176 | //FIXME: this is very redundant with set_age, refactor... 177 | var age = my.chart_age; 178 | if (age > 60 * 60 * 1) { 179 | data = data_ld; 180 | //console.log("LD") 181 | } else { 182 | data = data_hd; 183 | //console.log("HD",webGI.log.data.length) 184 | } 185 | if (!chart) { 186 | my.init(); 187 | } else { 188 | chart.updateOptions({ 189 | file: data, 190 | dateWindow: [my.now - my.chart_age * 1000, my.now] 191 | }); 192 | } 193 | //console.log(annotations); 194 | chart.setAnnotations(annotations); 195 | }; 196 | 197 | 198 | my.set_log_scale = function(enabled) { 199 | 200 | if (chart) chart.updateOptions({ 201 | logscale: enabled 202 | }); 203 | my.log_scale = enabled; 204 | }; 205 | 206 | my.set_colors = function(c) { 207 | 208 | if (chart) chart.updateOptions({ 209 | colors: c 210 | }); 211 | my.chart_colors = c; 212 | }; 213 | 214 | my.set_age = function(seconds) { 215 | 216 | my.chart_age = seconds; 217 | if (animtimer !== null) { 218 | clearTimeout(animtimer); 219 | } 220 | 221 | if (seconds > 60 * 60 * 1) { 222 | data = data_ld; 223 | } else { 224 | data = data_hd; 225 | } 226 | 227 | if (!chart) { 228 | my.init(); 229 | } else { 230 | chart.updateOptions({ 231 | file: data, 232 | dateWindow: desired_range 233 | }); 234 | } 235 | 236 | zoom(my.chart_age * 1000); 237 | chart.setAnnotations(annotations); 238 | }; 239 | 240 | my.enable = function() { 241 | container.show(); 242 | }; 243 | 244 | my.disable = function() { 245 | container.hide(); 246 | }; 247 | 248 | my.requestLog = function(age, hd) { 249 | 250 | var cmd = { 251 | "cmd": "read", 252 | "age": age, 253 | "hd": hd 254 | }; 255 | 256 | webGI.websocket.send(cmd); 257 | //console.log ("Requesting log (age " +webGI.log.chart_age +" )"); 258 | }; 259 | 260 | my.requestHistory = function(from, to) { 261 | 262 | var cmd = { 263 | "cmd": "history", 264 | "from": from, 265 | "to": to 266 | }; 267 | 268 | webGI.websocket.send(cmd); 269 | //console.log ("Requesting history"); 270 | }; 271 | 272 | my.getDoseRateAvg15m = function() { 273 | return data_hd[data_hd.length - 1][2]; 274 | }; 275 | 276 | my.getDose24h = function() { 277 | 278 | var first = data_ld_all[0]; 279 | var last = data_ld_all[data_ld_all.length - 1]; 280 | var counts = last.data.totalcount_dtc - first.data.totalcount_dtc; 281 | var cpm = counts / (24 * 60); 282 | var d24h = cpm * last.parameters.tube_factor * 24; 283 | return parseFloat(d24h.toFixed(3)); 284 | }; 285 | 286 | my.pushAnnotation = function(ts, text) { 287 | 288 | var cmd = { 289 | "cmd": "annotation", 290 | "timestamp": ts, 291 | "text": text 292 | }; 293 | 294 | webGI.websocket.send(cmd); 295 | //console.log ("Requesting history"); 296 | }; 297 | 298 | 299 | /*************************************************************************** 300 | * Private functions *******************************************************/ 301 | 302 | function chartApproachRange() { 303 | 304 | if (!desired_range) return; 305 | // go halfway there 306 | var range = chart.xAxisRange(); 307 | if (Math.abs(desired_range[0] - range[0]) < 60 && 308 | Math.abs(desired_range[1] - range[1]) < 60) { 309 | if (my.chart_age > 60 * 60 * 1) { 310 | data = data_ld; 311 | } else { 312 | data = data_hd; 313 | } 314 | chart.setAnnotations(annotations); 315 | chart.updateOptions({ 316 | dateWindow: desired_range, 317 | file: data 318 | }); 319 | // (do not set another timeout.) 320 | } else { 321 | var new_range; 322 | new_range = [0.5 * (desired_range[0] + range[0]), 323 | 0.5 * (desired_range[1] + range[1]) 324 | ]; 325 | chart.setAnnotations(annotations); 326 | chart.updateOptions({ 327 | dateWindow: new_range 328 | }); 329 | chartAnimate(); 330 | } 331 | } 332 | 333 | function chartAnimate() { 334 | animtimer = setTimeout(chartApproachRange, 50); 335 | } 336 | 337 | function zoom(age) { 338 | 339 | var w = chart.xAxisRange(); 340 | desired_range = [my.now - age, my.now]; 341 | chartAnimate(); 342 | } 343 | 344 | 345 | return my; 346 | 347 | }($)); 348 | -------------------------------------------------------------------------------- /software/public/assets/js/webGI_options.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Client/Server options & configuration management module 3 | */ 4 | 5 | // Create webGI object if neccessary 6 | if (typeof webGI === 'undefined') { 7 | webGI = {}; 8 | } 9 | 10 | // Add module to webGI namespace 11 | webGI.options = (function($) { 12 | 13 | /*************************************************************************** 14 | * Public attributes *******************************************************/ 15 | 16 | var my = { 17 | server: {}, 18 | client: { 19 | show_dtc: true 20 | } 21 | }; 22 | 23 | 24 | /*************************************************************************** 25 | * Private attributes ******************************************************/ 26 | 27 | 28 | 29 | /*************************************************************************** 30 | * Public functions ********************************************************/ 31 | 32 | my.save = function() { 33 | 34 | my.server.sim_dose_rate = parseFloat($('#server_cnf_sim_dose_rate').val()); 35 | 36 | // Get OpMode 37 | if ($('#server_cnf_gps_mode_mobile').is(':checked')) { 38 | my.server.opmode = "mobile"; 39 | } else { 40 | my.server.opmode = "stationary"; 41 | my.server.lat = parseFloat($('#server_cnf_node_lat').val()); 42 | my.server.lon = parseFloat($('#server_cnf_node_lon').val()); 43 | my.server.alt = parseFloat($('#server_cnf_node_alt').val()); 44 | } 45 | 46 | // Get Source Selection 47 | if ($('#server_cnf_source_env').is(':checked')) { 48 | my.server.source = "env"; 49 | } else if ($('#server_cnf_source_test').is(':checked')) { 50 | my.server.source = "test"; 51 | } else if ($('#server_cnf_source_sim').is(':checked')) { 52 | my.server.source = "sim"; 53 | } 54 | 55 | // Get Window/Sensitivity/Capability 56 | if ($('#cgw_abc').is(':checked')) { 57 | my.server.window = "abc"; 58 | } else if ($('#cgw_bc').is(':checked')) { 59 | my.server.window = "bc"; 60 | } else if ($('#cgw_c').is(':checked')) { 61 | my.server.window = "c"; 62 | } 63 | 64 | // Get Entropy Generator Setting 65 | my.server.entropy = $('#server_cnf_entropy_enabled').is(':checked'); 66 | 67 | var cmd = { 68 | "cmd": "save", 69 | "conf": my.server 70 | }; 71 | 72 | webGI.websocket.send(cmd); 73 | console.log("Saving options", my.server); 74 | my.request(); 75 | }; 76 | 77 | my.request = function() { 78 | 79 | var cmd = { 80 | 'cmd': 'get' 81 | }; 82 | 83 | webGI.websocket.send(cmd); 84 | }; 85 | 86 | my.reset = function() { 87 | console.log("clear pyGI conf/dynamic.cfg"); 88 | webGI.websocket.send({"cmd": "resetDynamicCfg"}); 89 | my.request(); 90 | }; 91 | 92 | my.startEntropyDownload = function() { 93 | var hiddenIFrameID = 'hiddenDownloader', 94 | iframe = document.getElementById(hiddenIFrameID); 95 | if (iframe === null) { 96 | iframe = document.createElement('iframe'); 97 | iframe.id = hiddenIFrameID; 98 | iframe.style.display = 'none'; 99 | document.body.appendChild(iframe); 100 | } 101 | iframe.src = "/webGI/data/entropy.bin"; 102 | }; 103 | 104 | my.resetEntropy = function() { 105 | console.log("Resetting entropy"); 106 | webGI.websocket.send({"cmd": "resetEntropy"}); 107 | my.request(); 108 | }; 109 | 110 | my.lin2log = function(position) { 111 | 112 | var minp = 0; 113 | var maxp = 100; 114 | var minv = Math.log(0.01); 115 | var maxv = Math.log(1000); 116 | var scale = (maxv - minv) / (maxp - minp); 117 | return Math.exp(minv + scale * (position - minp)); 118 | }; 119 | 120 | my.log2lin = function(value) { 121 | 122 | var minp = 0; 123 | var maxp = 100; 124 | var minv = Math.log(0.01); 125 | var maxv = Math.log(1000); 126 | var scale = (maxv - minv) / (maxp - minp); 127 | return (Math.log(value) - minv) / scale + minp; 128 | }; 129 | 130 | my.geoSnapshotCallback = function(position) { 131 | 132 | //console.log(position); 133 | $('#server_cnf_node_lat').val(position.coords.latitude.toFixed(5)); 134 | $('#server_cnf_node_lon').val(position.coords.longitude.toFixed(5)); 135 | $('#server_cnf_node_alt').val(position.coords.altitude); 136 | }; 137 | 138 | my.addOptionCheckbox = function(parent_id, id, label, checked) { 139 | 140 | checked = (typeof checked === "undefined") ? false : checked; 141 | var content = '
  • '; 142 | content += ''; 143 | content += ''; 147 | content += '
  • '; 148 | $('#' + parent_id).append(content); 149 | }; 150 | 151 | my.update = function(msg) { 152 | //console.log("Options:", msg); 153 | document.getElementById('cnf_node_uuid').innerHTML = msg.uuid; 154 | document.getElementById('cnf_node_name').innerHTML = msg.name; 155 | //$('#cnf_node_uuid').text(msg.uuid); 156 | //$('#cnf_node_name').text(msg.name); 157 | 158 | // OPMode 159 | if (msg.opmode === "stationary") { 160 | $('#server_cnf_gps_mode_stationary').prop('checked', true); 161 | } else if (msg.opmode === "mobile") { 162 | $('#server_cnf_gps_mode_mobile').prop('checked', true); 163 | } 164 | 165 | // Geostamp 166 | document.getElementById('server_cnf_node_lat').value = msg.lat; 167 | document.getElementById('server_cnf_node_lon').value = msg.lon; 168 | document.getElementById('server_cnf_node_alt').value = msg.alt; 169 | //$('#server_cnf_node_lat').val(msg.lat); 170 | //$('#server_cnf_node_lon').val(msg.lon); 171 | //$('#server_cnf_node_alt').val(msg.alt); 172 | 173 | // Data Sources 174 | $('#server_cnf_source_' + msg.source).prop('checked', true); 175 | 176 | // Window/Capabilties/Sensitivity 177 | $('#cgw_' + msg.window).prop('checked', true); 178 | 179 | // Tick Simulator Dose Rate 180 | my.server.sim_dose_rate = msg.sim_dose_rate; 181 | 182 | document.getElementById('simRanger').value = webGI.options.log2lin(my.server.sim_dose_rate); 183 | //$('#simRanger').val(webGI.options.log2lin(my.server.sim_dose_rate)); 184 | 185 | if (my.server.sim_dose_rate >= 10) { 186 | my.server.sim_dose_rate = my.server.sim_dose_rate.toFixed(0); 187 | $('#server_cnf_sim_dose_rate').css({ 188 | "color": "#F5C43C" 189 | }); 190 | } else { 191 | $('#server_cnf_sim_dose_rate').css({ 192 | "color": "#75890c" 193 | }); 194 | } 195 | 196 | document.getElementById('server_cnf_sim_dose_rate').value = my.server.sim_dose_rate; 197 | //$('#server_cnf_sim_dose_rate').val(my.server.sim_dose_rate); 198 | 199 | // Entropy Generator 200 | if (msg.entropy === false) { 201 | $('#server_cnf_entropy_disabled').prop('checked', true); 202 | } else { 203 | $('#server_cnf_entropy_enabled').prop('checked', true); 204 | } 205 | 206 | document.getElementById('server_entropy_pool').value = msg.entropy_pool; 207 | //$('#server_entropy_pool').val(msg.entropy_pool); 208 | } 209 | /*************************************************************************** 210 | * Private functions *******************************************************/ 211 | 212 | 213 | 214 | 215 | return my; 216 | 217 | }($)); 218 | -------------------------------------------------------------------------------- /software/public/assets/js/webGI_status.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Status/Data websocket module 3 | */ 4 | 5 | // Create webGI object if neccessary 6 | if (typeof webGI === 'undefined') { 7 | webGI = {}; 8 | } 9 | 10 | // Add status module to webGI namespace 11 | webGI.status = (function($) { 12 | 13 | /*************************************************************************** 14 | * Public attributes ******************************************************/ 15 | 16 | var my = {}; 17 | 18 | 19 | /*************************************************************************** 20 | * Private attributes ******************************************************/ 21 | 22 | var count_unit = 'CPM'; 23 | var ws_status = null; 24 | 25 | 26 | /*************************************************************************** 27 | * Public functions ********************************************************/ 28 | 29 | my.init = function() { 30 | 31 | // Add Checkboxes to client settings panel 32 | // and set check status according to config 33 | 34 | webGI.options.addOptionCheckbox( 35 | 'client_settings', 36 | 'cnf_dtc_enabled', 37 | 'Dead-Time Compensation', 38 | (webGI.conf.dtc_enabled === 1) ? true : false 39 | ); 40 | }; 41 | 42 | my.init_socket = function() { 43 | ws_status = new WebSocket(webGI.conf.websocket_host + '/ws_status'); 44 | 45 | ws_status.onopen = function() { 46 | $('#modalError').removeClass('md-show'); 47 | //console.log('Status Update socket opened'); 48 | }; 49 | 50 | ws_status.onmessage = function(e) { 51 | var msg = JSON.parse(e.data); 52 | //console.log(msg); 53 | switch (msg.type) { 54 | case 'geigerjson': 55 | my.update(msg); 56 | webGI.livechart.now = parseInt(msg.timestamp * 1000); 57 | webGI.tracer.add(parseInt(msg.data.cps_dtc)); 58 | break; 59 | 60 | default: 61 | console.log('INVALID MESSAGE', msg); 62 | } 63 | }; 64 | 65 | ws_status.onclose = function() { 66 | ws_status = new WebSocket(webGI.conf.websocket_host + "/ws_status"); 67 | 68 | showErrorModal( 69 | 'Websocket Error', 70 | '

    Wheeeeh, I lost my sockets. Either the server has gone down or the network connection is unreliable or stalled.

    Possible solutions:

    • Is the pyGI daemon running on the Pi?
    • Enable/toggle your WIFI connection
    ' 71 | ); 72 | 73 | setTimeout(function() { 74 | my.init_socket(); 75 | webGI.livechart.init_socket(); 76 | }, 5000); 77 | //console.log ("Status socket reset"); 78 | }; 79 | }; 80 | 81 | my.enable = function() { 82 | $('#gaugeContainer').show(); 83 | }; 84 | 85 | my.disable = function() { 86 | $('#gaugeContainer').hide(); 87 | 88 | }; 89 | 90 | my.show_radcon = function() { 91 | $('#modalRADCON').addClass('md-show'); 92 | }; 93 | 94 | my.toggle_counter_unit = function() { 95 | if (count_unit == 'CPM') { 96 | $('#count_unit').html('CPS'); 97 | count_unit = 'CPS'; 98 | } else { 99 | $('#count_unit').html('CPM'); 100 | count_unit = 'CPM'; 101 | } 102 | }; 103 | 104 | my.update = function(msg) { 105 | 106 | if (count_unit == 'CPM') { 107 | document.getElementById('count_val').innerHTML = parseInt(msg.data.cpm_dtc); 108 | //$('#count_val').html(parseInt(msg.data.cpm_dtc)); 109 | 110 | } else if (count_unit == 'CPS') { 111 | document.getElementById('count_val').innerHTML = parseInt(msg.data.cps_dtc); 112 | //$('#count_val').html(parseInt(msg.data.cps_dtc)); 113 | } 114 | 115 | if (msg.data.source == 'sim') { 116 | $('#simNotify').addClass('init-simNotify'); 117 | } else { 118 | $('#simNotify').removeClass('init-simNotify'); 119 | } 120 | 121 | var edr = parseFloat(msg.data.edr); 122 | 123 | // RADCON class identification and UI reaction 124 | var s = 0.1; 125 | //var last = document.getElementById('lvl_val').innerHTML; 126 | 127 | for (var c = 0; c <= 8; c++) { 128 | if (edr < s) { 129 | $('#statusGauge').attr('max', s); 130 | 131 | document.getElementById('lvl_val').innerHTML = c; 132 | //document.getElementById('status_radcon').innerHTML = c; 133 | $('.rc-row').removeClass('current'); 134 | $('#rc' + c).addClass('current'); 135 | 136 | if (c < 3) { 137 | $('.rc-cat').removeClass('current'); 138 | $('#rcCatLow').addClass('current'); 139 | $('#edr_val, #status_edr_val, #edr_unit, #lvl_val, #lvl_unit').removeClass('yellow red'); 140 | $('#edr_val, #status_edr_val, #edr_unit, #lvl_val, #lvl_unit').addClass('green'); 141 | //webGI.livechart.set_colors(['#677712','yellow']); 142 | } else if (c < 6) { 143 | $('.rc-cat').removeClass('current'); 144 | $('#rcCatMed').addClass('current'); 145 | $('#edr_val, #status_edr_val, #edr_unit, #lvl_val, #lvl_unit').removeClass('green red'); 146 | $('#edr_val, #status_edr_val, #edr_unit, #lvl_val, #lvl_unit').addClass('yellow'); 147 | //webGI.livechart.set_colors(['#F5C43C','yellow']); 148 | } else { 149 | $('.rc-cat').removeClass('current'); 150 | $('#rcCatHigh').addClass('current'); 151 | $('#edr_val, #status_edr_val, #edr_unit, #lvl_val, #lvl_unit').removeClass('green yellow'); 152 | $('#edr_val, #status_edr_val, #edr_unit, #lvl_val, #lvl_unit').addClass('red'); 153 | //webGI.livechart.set_colors(['#ff0000','yellow']); 154 | } 155 | 156 | break; 157 | } else { 158 | s = s * 10; 159 | } 160 | } 161 | 162 | // Automatic unit switching 163 | if (edr < 1000) { 164 | document.getElementById('edr_unit').innerHTML = 'uSv/h'; 165 | } else if (edr < 1000000) { 166 | document.getElementById('edr_unit').innerHTML = 'mSv/h'; 167 | edr = edr / 1000; 168 | } else { 169 | document.getElementById('edr_unit').innerHTML = 'Sv/h'; 170 | edr = edr / 1000000; 171 | } 172 | 173 | document.getElementById('edr_val').innerHTML = edr.toFixed(2); 174 | document.getElementById('status_edr_val').innerHTML = edr.toFixed(2); 175 | //document.getElementById('statusGauge').value = edr; 176 | document.getElementById('status_cps').innerHTML = parseInt(msg.data.cps_dtc); 177 | document.getElementById('status_cpm').innerHTML = parseInt(msg.data.cpm_dtc); 178 | document.getElementById('status_rem').innerHTML = (edr / 10).toFixed(2); 179 | document.getElementById('status_avg_15min').innerHTML = webGI.livechart.getDoseRateAvg15m(); 180 | document.getElementById('status_24h_dose').innerHTML = webGI.livechart.getDose24h(); 181 | 182 | var etm = 10000 / edr; 183 | var d = parseInt(etm / 24); 184 | var h = parseInt(etm % 24); 185 | 186 | /* 187 | if (d > 365) { 188 | document.getElementById('status_etm').innerHTML = 'Indefinitely'; 189 | //$('#status_etm').val(d+' '+ h); 190 | } else if (d > 100) { 191 | document.getElementById('status_etm').innerHTML = d; 192 | } else { 193 | document.getElementById('status_etm').innerHTML = d + ' ' + h; 194 | } 195 | */ 196 | // Analyze data for alerts 197 | webGI.alert.analyze(edr,c); 198 | 199 | }; 200 | 201 | 202 | /*************************************************************************** 203 | * Private functions *******************************************************/ 204 | 205 | 206 | return my; 207 | 208 | }($)); 209 | -------------------------------------------------------------------------------- /software/public/assets/js/webGI_ticker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Audio feedback/tick module 3 | */ 4 | 5 | // Create webGI object if neccessary 6 | if (typeof webGI === 'undefined') { 7 | webGI = {}; 8 | } 9 | 10 | // Add ticker module to webGI namespace 11 | webGI.ticker = (function($) { 12 | 13 | /*************************************************************************** 14 | * Public attributes *******************************************************/ 15 | 16 | var my = { 17 | enabled: false 18 | }; 19 | 20 | 21 | /*************************************************************************** 22 | * Private attributes ******************************************************/ 23 | 24 | var tick_snd = new Audio("assets/snd/tock.wav"); 25 | 26 | 27 | /*************************************************************************** 28 | * Public functions ********************************************************/ 29 | 30 | my.init = function() { 31 | // Add Checkbox to client settings panel 32 | webGI.options.addOptionCheckbox( 33 | 'client_settings', 34 | 'cnf_silent', 35 | 'Silent Mode (No Audio Feedback/Alerts)' 36 | ); 37 | }; 38 | 39 | my.play_ticks = function(count) { 40 | if (my.enabled) { 41 | for (var i = 0; i < parseInt(count); i++) { 42 | setTimeout(function() { 43 | tick_snd.play(); 44 | }, Math.random() * 200); 45 | } 46 | } 47 | } 48 | my.enable = function() { 49 | my.enabled = true; 50 | webGI.websocket.send({"cmd":"send_ticks","state":"on"}); 51 | }; 52 | 53 | my.disable = function() { 54 | my.enabled = false; 55 | webGI.websocket.send({"cmd":"send_ticks","state":"off"}); 56 | }; 57 | 58 | 59 | /*************************************************************************** 60 | * Private functions *******************************************************/ 61 | 62 | 63 | return my; 64 | 65 | }($)); 66 | -------------------------------------------------------------------------------- /software/public/assets/js/webGI_tracer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 2D/hardware accelerated canvas particle tracer/visualizer 3 | */ 4 | 5 | // Create webGI object if neccessary 6 | if (typeof webGI === 'undefined') { 7 | webGI = {}; 8 | } 9 | 10 | // Add tracer module to webGI namespace 11 | webGI.tracer = (function($) { 12 | 13 | /*************************************************************************** 14 | * Public attributes *******************************************************/ 15 | 16 | var my = { 17 | container_id: 'traceContainer', 18 | canvas_id: 'traceCanvas' 19 | }; 20 | 21 | 22 | /*************************************************************************** 23 | * Private attributes ******************************************************/ 24 | 25 | var particles = {}; 26 | var draw_interval = null; 27 | var active = false; 28 | 29 | 30 | /*************************************************************************** 31 | * Public functions ********************************************************/ 32 | 33 | my.enable = function() { 34 | 35 | $('#' + my.container_id).show(); 36 | active = true; 37 | canvas = document.getElementById(my.canvas_id); 38 | var ctx = canvas.getContext('2d'); 39 | particles = {}; 40 | draw_interval = setInterval(draw, 33); 41 | }; 42 | 43 | my.disable = function() { 44 | 45 | $('#' + my.container_id).hide(); 46 | particles = {}; 47 | active = false; 48 | if (draw_interval !== null) clearInterval(draw_interval); 49 | }; 50 | 51 | my.add = function(amount) { 52 | 53 | if (active) { 54 | for (var i = 0; i < amount; i++) { 55 | setTimeout(function() { 56 | particles[Math.random()] = new createParticle(); 57 | }, 58 | Math.random() * 1000); 59 | } 60 | } 61 | }; 62 | 63 | 64 | /*************************************************************************** 65 | * Private functions *******************************************************/ 66 | 67 | function createParticle() { 68 | 69 | var b = Math.random() * 128 + 128 >> 0; 70 | this.color = 'rgba(' + b + ',' + b + ',' + b + ',0.6)'; 71 | 72 | this.x = Math.random() * canvas.width; 73 | this.y = 0; //-Math.random()*webGI.trace.canvas.height; 74 | 75 | this.vx = 0; 76 | this.vy = Math.random() * 4 + 2; 77 | } 78 | 79 | function draw() { 80 | 81 | var W = canvas.width; 82 | var H = canvas.height; 83 | var ctx = canvas.getContext('2d'); 84 | 85 | ctx.globalCompositeOperation = 'source-over'; 86 | ctx.fillStyle = 'rgba(30,30,30, 0.7)'; 87 | ctx.fillRect(0, 0, W, H); 88 | ctx.globalCompositeOperation = 'lighter'; 89 | 90 | // Lets draw particles from the array now 91 | $.each(particles, function(t, p) { 92 | 93 | ctx.beginPath(); 94 | ctx.fillStyle = p.color; 95 | ctx.fillRect(p.x, p.y, 1, p.vy); 96 | ctx.fillStyle = 'rgba(117,137,12,1)'; 97 | ctx.fillRect(p.x, p.y + p.vy, 1, 2); 98 | 99 | p.x += p.vx; 100 | p.y += p.vy; 101 | p.vy += Math.random() * p.y / 25; 102 | 103 | // To prevent the balls from moving out of the canvas 104 | if (p.x < -50) p.x = W + 50; 105 | if (p.y < -50) p.y = H + 50; 106 | if (p.x > W) p.x = -50; 107 | if (p.y > H) { 108 | delete particles[t]; 109 | } 110 | }); 111 | } 112 | 113 | 114 | return my; 115 | 116 | }($)); 117 | -------------------------------------------------------------------------------- /software/public/assets/js/webGI_websocket.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Status/Data websocket module 3 | */ 4 | 5 | // Create webGI object if neccessary 6 | if (typeof webGI === 'undefined') { 7 | webGI = {}; 8 | } 9 | 10 | // Add websocket module to webGI namespace 11 | webGI.websocket = (function($) { 12 | 13 | /*************************************************************************** 14 | * Public attributes ******************************************************/ 15 | 16 | var my = {}; 17 | 18 | 19 | /*************************************************************************** 20 | * Private attributes ******************************************************/ 21 | 22 | var ws = null; 23 | 24 | 25 | /*************************************************************************** 26 | * Public functions ********************************************************/ 27 | 28 | my.init = function() { 29 | ws = new WebSocket(webGI.conf.websocket_host + '/ws'); 30 | 31 | ws.onopen = function() { 32 | $('#modalError').removeClass('md-show'); 33 | console.log('websocket opened'); 34 | 35 | //FIXME: do we need the timeout? 36 | setTimeout(function() { 37 | webGI.livechart.requestLog(60 * 60 * 1, true); 38 | webGI.livechart.requestLog(60 * 60 * 24, false); 39 | 40 | //FIXME: this belongs to history, not livechart! 41 | webGI.livechart.requestHistory(null, null); 42 | 43 | webGI.options.request(); 44 | }, 100); 45 | }; 46 | 47 | ws.onmessage = function(e) { 48 | var msg = JSON.parse(e.data); 49 | console.log("websocket receiving",msg.type,msg); 50 | switch (msg.type) { 51 | case 'geigerjson-status': 52 | webGI.status.update(msg); 53 | webGI.livechart.now = parseInt(msg.timestamp * 1000); 54 | webGI.tracer.add(parseInt(msg.data.cps_dtc)); 55 | break; 56 | case "tick": 57 | webGI.ticker.play_ticks(msg.count); 58 | break; 59 | //Log 60 | case "geigerjson": 61 | webGI.livechart.update(msg); 62 | break; 63 | case "history": 64 | webGI.livechart.updateBacklog(msg); 65 | break; 66 | case "static_history": 67 | webGI.history.update(msg); 68 | break; 69 | 70 | //Config 71 | case 'geigerconf': 72 | webGI.options.update(msg); 73 | break; 74 | 75 | default: 76 | console.log('INVALID MESSAGE', msg); 77 | } 78 | }; 79 | 80 | ws.onclose = function() { 81 | ws = new WebSocket(webGI.conf.websocket_host + "/ws"); 82 | 83 | showErrorModal( 84 | 'Websocket Error', 85 | '

    Wheeeeh, I lost my sockets. Either the server has gone down or the network connection is unreliable or stalled.

    Possible solutions:

    • Is the pyGI daemon running on the Pi?
    • Enable/toggle your WIFI connection
    ' 86 | ); 87 | 88 | setTimeout(function() { 89 | my.init(); 90 | }, 5000); 91 | console.log ("websocket reset"); 92 | }; 93 | }; 94 | 95 | my.send = function(msg){ 96 | console.log("websocket sending ",msg); 97 | ws.send(JSON.stringify(msg)) 98 | } 99 | 100 | /*************************************************************************** 101 | * Private functions *******************************************************/ 102 | 103 | 104 | return my; 105 | 106 | }($)); 107 | -------------------------------------------------------------------------------- /software/public/assets/snd/tick.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/snd/tick.mp3 -------------------------------------------------------------------------------- /software/public/assets/snd/tock.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/snd/tock.wav -------------------------------------------------------------------------------- /software/public/assets/snd/ui-bell.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/assets/snd/ui-bell.mp3 -------------------------------------------------------------------------------- /software/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/public/favicon.ico -------------------------------------------------------------------------------- /software/pyGI/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollo-ng/PiGI/384bfe5be5d7aecf33e80803b5db012b23a481a7/software/pyGI/__init__.py -------------------------------------------------------------------------------- /software/pyGI/configurator.py: -------------------------------------------------------------------------------- 1 | import ConfigParser 2 | import logging 3 | import sys,os,uuid 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | FILENAME_DEFAULT = 'default.cfg' 8 | FILENAME_LOCAL = 'local.cfg' 9 | FILENAME_DYNAMIC = 'dynamic.cfg' 10 | FILENAME_UUID = 'uuid.cfg' 11 | CONF_DIR = os.path.join(sys.path[0],'conf') 12 | 13 | PATH_DEFAULT = os.path.join(CONF_DIR,FILENAME_DEFAULT) 14 | PATH_LOCAL = os.path.join(CONF_DIR,FILENAME_LOCAL) 15 | PATH_DYNAMIC = os.path.join(CONF_DIR,FILENAME_DYNAMIC) 16 | PATH_UUID = os.path.join(CONF_DIR,FILENAME_UUID) 17 | 18 | class Configurator(): 19 | def __init__(self): 20 | self.static_conf = ConfigParser.SafeConfigParser() 21 | 22 | #uuid 23 | 24 | try: 25 | self.static_conf.readfp(open(PATH_UUID)) 26 | log.info("node uuid: %s"%self.static_conf.get('node','uuid')) 27 | except (IOError, ConfigParser.NoOptionError, ConfigParser.NoSectionError) as e: 28 | log.warn("No uuid set!") 29 | new_uuid = str(uuid.uuid1()) 30 | log.warn("Setting new uuid: %s"%new_uuid) 31 | self.static_conf = ConfigParser.SafeConfigParser() 32 | self.static_conf.add_section('node') 33 | self.static_conf.set('node','uuid',new_uuid) 34 | with open(PATH_UUID,'wb') as f: 35 | self.static_conf.write(f) 36 | 37 | self.static_conf.readfp(open(PATH_DEFAULT)) 38 | log.info('reading configuration default.cfg') 39 | 40 | 41 | additionals = self.static_conf.read(PATH_LOCAL) 42 | for f in additionals: 43 | log.info('reading configuration %s'%f) 44 | 45 | 46 | self.dynamic_conf = ConfigParser.SafeConfigParser() 47 | self.read_dynamic() 48 | 49 | def read_dynamic(self): 50 | dyn = self.dynamic_conf.read(PATH_DYNAMIC) 51 | for f in dyn: 52 | log.info('reading configuration %s'%f) 53 | 54 | def write_dynamic(self): 55 | with open(PATH_DYNAMIC,'wb') as f: 56 | self.dynamic_conf.write(f) 57 | 58 | def clear_dynamic(self): 59 | try: 60 | os.remove(PATH_DYNAMIC) 61 | except OSError: 62 | pass 63 | 64 | self.dynamic_conf = ConfigParser.SafeConfigParser() 65 | self.read_dynamic() 66 | 67 | def get(self,section,option): 68 | try: 69 | return self.dynamic_conf.get(section,option) 70 | except ConfigParser.NoOptionError: 71 | pass 72 | except ConfigParser.NoSectionError: 73 | pass 74 | 75 | return self.static_conf.get(section,option) 76 | 77 | def getint(self,section,option): 78 | return int(self.get(section,option)) 79 | 80 | def getfloat(self,section,option): 81 | return float(self.get(section,option)) 82 | 83 | def getboolean(self,section,option): 84 | v = self.get(section,option) 85 | v_low = v.lower() 86 | if v_low in ["1","yes","true","on"]: 87 | return True 88 | elif v_low in ["0","no","false","off"]: 89 | return False 90 | else: 91 | raise ValueError("value '%s' (option '%s' section '%s') could not be parsed as boolean."%(v,option,section)) 92 | 93 | def set(self,section,option,value): 94 | if not self.static_conf.has_section(section): 95 | raise ConfigParser.NoSectionError(section) 96 | if not self.static_conf.has_option(section,option): 97 | raise ConfigParser.NoOptionError(option,section) 98 | if not self.dynamic_conf.has_section(section): 99 | self.dynamic_conf.add_section(section) 100 | self.dynamic_conf.set(section,option,value) 101 | 102 | cfg = Configurator() 103 | 104 | if __name__ == "__main__": 105 | logging.basicConfig(level=1) 106 | cfg = Configurator() 107 | print cfg.get('server','port') 108 | cfg.set('server','port','80') 109 | print cfg.get('server','port') 110 | cfg.write_dynamic() 111 | cfg.read_dynamic() 112 | print cfg.get('server','port') 113 | #print cfg.static_conf.get('server','2p') 114 | -------------------------------------------------------------------------------- /software/pyGI/entropygenerator.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import logging 3 | import threading 4 | import time 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | class EntropyGenerator(threading.Thread): 9 | def __init__(self,outfile): 10 | self.outfile = outfile 11 | 12 | threading.Thread.__init__(self) 13 | self.daemon = True 14 | 15 | #setup vars for randomness production 16 | self.tick_counter = 0 17 | self.toggle = False 18 | self.t0 = self.t1 = self.t2 = datetime.datetime.now() 19 | self.bitstring = "" 20 | 21 | self.start() 22 | 23 | def run(self): 24 | log.info("Starting entropy generator") 25 | while True: 26 | time.sleep(1) 27 | self.handle_bitstring() 28 | 29 | def tick (self): 30 | # This works like this: 31 | # time: |------------|-------------|-----------|-----------| 32 | # tick 0: t0 33 | # tick 1: t0 t1 34 | # tick 2: t2 t1 t0 35 | # d0 d1 36 | # tick 3: t2 t0 t1 37 | # tick 4: t2 t1 t0 38 | # dO d1 39 | 40 | self.tick_counter += 1 41 | if (self.tick_counter % 2) == 0: 42 | self.t2 = self.t0 43 | self.t0 = datetime.datetime.now() 44 | d0 = self.t1 - self.t2 45 | d1 = self.t0 - self.t1 46 | 47 | if d0 > d1: 48 | self.bitstring += "1" if self.toggle else "0" 49 | elif d0 < d1: 50 | self.bitstring += "0" if self.toggle else "1" 51 | else: #d0 = d1 52 | print "Collision" 53 | 54 | self.toggle = not self.toggle 55 | 56 | else: 57 | self.t1 = datetime.datetime.now() 58 | 59 | 60 | def handle_bitstring(self): 61 | with open(self.outfile, "ab") as f: 62 | while len(self.bitstring)>=8: 63 | byte_bin = self.bitstring[:8] 64 | self.bitstring = self.bitstring[8:] 65 | byte_int = int(byte_bin,2) 66 | byte_hex = hex(byte_int) 67 | byte_chr = chr(byte_int) 68 | log.debug("new random byte:%s %3d %4s %s"%(byte_bin,byte_int, 69 | byte_hex,byte_chr)) 70 | f.write(byte_chr) 71 | 72 | 73 | if __name__ == "__main__": 74 | logging.basicConfig(level="DEBUG") 75 | eg = EntropyGenerator("test.bin") 76 | while True: 77 | eg.tick() 78 | time.sleep(0.1) 79 | -------------------------------------------------------------------------------- /software/pyGI/geigerclient.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import json 3 | import time 4 | import datetime 5 | import uuid 6 | import os 7 | import sys 8 | 9 | from geventwebsocket import WebSocketError 10 | from configurator import cfg 11 | import geigercounter 12 | 13 | import logging 14 | 15 | log = logging.getLogger(__name__) 16 | script_dir = sys.path[0] 17 | 18 | class WebSocketClientConnector(): 19 | def __init__(self,ws): 20 | self.ws = ws 21 | self.session_id = uuid.uuid1() 22 | self.active = True 23 | self.send_ticks = False 24 | self.send_status = True 25 | self.send_log = True 26 | 27 | def send(self,msg): 28 | msg_json = json.dumps(msg) 29 | try: 30 | self.ws.send(msg_json) 31 | except WebSocketError: 32 | self.active = False 33 | log.error("could not write to socket %s (client %s)"%(self.ws,self.session_id)) 34 | except Exception, e: 35 | self.active = False 36 | log.error(e) 37 | 38 | def receive_commands(self,handler): 39 | #FIXME: some commands need clearer names 40 | #FIXME: more robust error/edgecase handling 41 | while True: 42 | try: 43 | message = self.ws.receive() 44 | if message is None: 45 | raise WebSocketError 46 | log.info("Received : %s" % message) 47 | msg = json.loads(message) 48 | cmd = msg.get("cmd") 49 | 50 | if not cmd: 51 | log.error("Received something, but not a command: %s"%msg) 52 | 53 | #Ticks 54 | if cmd == "send_ticks": 55 | if msg.get("state") == "on": 56 | self.send_ticks = True 57 | elif msg.get("state") == "off": 58 | self.send_ticks = False 59 | else: 60 | log.error("Invalid set_ticks command: %s"%msg) 61 | 62 | #Log 63 | elif cmd == "read": 64 | age_seconds = int(msg.get("age",60*60)); 65 | if msg.get("hd"): 66 | handler.send_log(self,age=age_seconds,amount=None) 67 | else: 68 | handler.send_log(self,age=age_seconds,amount=1000) 69 | elif cmd == "history": 70 | age_from = msg.get("from") 71 | age_to = msg.get("to") 72 | #log.info("From %s to %s"%(str(age_from),str(age_to))) 73 | handler.send_log(self,start=age_from,end=age_to,amount=1000,static=True) 74 | elif cmd == "annotation": 75 | ts = msg.get("timestamp") 76 | text = msg.get("text") 77 | handler.geigerlog.set_annotation(ts,text) 78 | handler.send_log(start=age_from,end=age_to,amount=1000,static=True) 79 | 80 | #Config 81 | elif cmd == "get": 82 | try: 83 | entropy_pool = os.path.getsize(cfg.get('entropy','filename')) 84 | except (IOError, OSError): 85 | entropy_pool = 0 86 | conf = { 87 | "type": "geigerconf", 88 | "uuid": cfg.get('node','uuid'), 89 | "name": cfg.get('node','name'), 90 | "opmode": cfg.get('node','opmode'), 91 | "lat": cfg.getfloat('node','lat'), 92 | "lon": cfg.getfloat('node','lon'), 93 | "alt": cfg.getfloat('node','alt'), 94 | "sim_dose_rate": cfg.getfloat('geigercounter','sim_dose_rate'), 95 | "window": cfg.get('geigercounter','window'), 96 | "source": cfg.get('geigercounter','source') if geigercounter.gpio_available else "sim", 97 | "entropy": cfg.getboolean('entropy','enable'), 98 | "entropy_pool": entropy_pool 99 | } 100 | self.send(conf) 101 | 102 | elif cmd == "save": 103 | for field in ["lat","lon","alt","opmode"]: 104 | val = msg["conf"].get(field) 105 | if not val is None: 106 | cfg.set('node',field,str(val)) 107 | 108 | for field in ["window","source","sim_dose_rate"]: 109 | val = msg["conf"].get(field) 110 | if not val is None: 111 | cfg.set('geigercounter',field,str(val)) 112 | 113 | entropy_enable = msg["conf"].get("entropy") 114 | if not entropy_enable is None: 115 | cfg.set('entropy','enable',str(entropy_enable)) 116 | 117 | cfg.write_dynamic() 118 | cfg.read_dynamic() 119 | elif cmd == "resetEntropy": 120 | log.info("Resetting entropy file") 121 | os.remove(os.path.join(script_dir,cfg.get('entropy','filename'))) 122 | elif cmd == "resetDynamicCfg": 123 | log.info("Resetting client config") 124 | cfg.clear_dynamic() 125 | 126 | except WebSocketError: 127 | break 128 | log.info("websocket closed %s (client %s)"%(self.ws.path,self.session_id)) 129 | 130 | class ClientsHandler(): 131 | def __init__(self,geiger,geigerlog): 132 | self.clients = [] 133 | self.geiger = geiger 134 | self.geigerlog = geigerlog 135 | 136 | status_thread = threading.Thread(target=self._loop_status) 137 | ticks_thread = threading.Thread(target=self._loop_ticks) 138 | log_thread = threading.Thread(target=self._loop_log) 139 | 140 | for thread in [status_thread, ticks_thread, log_thread]: 141 | thread.daemon = True 142 | thread.start() 143 | 144 | def add(self,client): 145 | if client in self.clients: 146 | self.clients.remove(client) 147 | self.clients.append(client) 148 | 149 | def send_log(self,client,start=None,end=None,age=None,amount=10,static=False): 150 | if age and age<60*60*2: #2hours 151 | amount = None 152 | history = self.geigerlog.get_log_entries(start=start,end=end,age=age,amount=amount) 153 | logtype = "static_history" if static else "history" 154 | hd = False if amount else True 155 | msg = {"type":logtype,"log":history,"hd":hd} 156 | log.info("sending %s"%(logtype)) 157 | self.send_if_active(client,msg) 158 | 159 | def send_if_active(self,client,msg): 160 | if client.active: 161 | client.send(msg) 162 | else: 163 | self.clients.remove(client) 164 | 165 | def _loop_status(self): 166 | log.info("Starting status update loop.") 167 | while True: 168 | msg = self.geiger.get_state() 169 | msg["type"]="geigerjson-status" 170 | log.debug("broadcasting status %s"%msg) 171 | for client in self.clients: 172 | if client.send_status: 173 | self.send_if_active(client,msg) 174 | time.sleep(1) 175 | 176 | def _loop_ticks(self): 177 | last_ticks = self.geiger.totalcount 178 | log.info("Starting ticks update loop") 179 | while True: 180 | ticks = self.geiger.totalcount-last_ticks 181 | last_ticks = self.geiger.totalcount 182 | if ticks > 0: 183 | for client in self.clients: 184 | if client.send_ticks: 185 | self.send_if_active(client,{"type":"tick", "count":ticks}) 186 | time.sleep(0.2) 187 | 188 | def _loop_log(self): 189 | my_last_log = None 190 | log.info("Starting log update loop") 191 | while True: 192 | time.sleep(1) 193 | log_last_log = self.geigerlog.last_log 194 | if log_last_log: 195 | if my_last_log != log_last_log: 196 | for client in self.clients: 197 | if client.send_log: 198 | self.send_if_active(client,log_last_log) 199 | my_last_log = log_last_log 200 | -------------------------------------------------------------------------------- /software/pyGI/geigercounter.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import os 3 | import time 4 | import random 5 | import datetime 6 | import logging 7 | 8 | from collections import deque 9 | from configurator import cfg 10 | from entropygenerator import EntropyGenerator 11 | 12 | log = logging.getLogger(__name__) 13 | 14 | try: 15 | import RPi.GPIO as GPIO 16 | gpio_available = True 17 | except ImportError: 18 | log.info("+---------------------------------------------------------------------------+") 19 | log.info("| Could not import RPi.GPIO python module. |") 20 | log.info("| I'm assuming you are in development/show mode on another host system |") 21 | log.info("| If this is a Raspberry PI/PiGI and you want real counts, install RPi.GPIO |") 22 | log.info("+---------------------------------------------------------------------------+") 23 | log.info("Engaging TickSimulator with an avg. radiation level of %(edr)s uSv/h instead" % {"edr": cfg.getfloat('geigercounter','sim_dose_rate')}) 24 | gpio_available = False 25 | 26 | class TickSimulator (threading.Thread): 27 | def __init__(self, geiger): 28 | threading.Thread.__init__(self) 29 | self.daemon = True 30 | self.geiger = geiger 31 | log.info("Starting tick simulator") 32 | 33 | def run(self): 34 | while True: 35 | ratefactor = cfg.getfloat('geigercounter','tube_rate_factor') 36 | simrate = cfg.getfloat('geigercounter','sim_dose_rate') 37 | rate = simrate/ratefactor 38 | time.sleep(random.random()/rate*120) 39 | self.geiger.tick() 40 | 41 | 42 | class Geigercounter (threading.Thread): 43 | def __init__(self,total=0,total_dtc=0): 44 | log.info("Starting geigercounter") 45 | threading.Thread.__init__(self) 46 | self.daemon = True 47 | self.socket = None 48 | self.totalcount=total 49 | self.totalcount_dtc=total_dtc 50 | 51 | if cfg.getboolean('entropy','enable'): 52 | self.entropygenerator = EntropyGenerator(cfg.get('entropy','filename')) 53 | else: 54 | self.entropygenerator = None 55 | 56 | 57 | self.reset() 58 | self.start() 59 | 60 | def reset(self): 61 | self.count=0 62 | self.cps=0 63 | self.cpm=0 64 | self.cps_dtc=0 65 | self.cpm_dtc=0 66 | self.edr=0 67 | 68 | def tick(self, pin=None): 69 | self.count += 1 70 | self.totalcount += 1 71 | self.totalcount_dtc += 1 72 | if self.entropygenerator: 73 | self.entropygenerator.tick() 74 | 75 | def run(self): 76 | if gpio_available: 77 | GPIO.setmode(GPIO.BCM) 78 | gpio_port = cfg.getint('geigercounter','gpio_port') 79 | GPIO.setup(gpio_port,GPIO.IN) 80 | GPIO.add_event_detect(gpio_port,GPIO.FALLING) 81 | GPIO.add_event_callback(gpio_port,self.tick) 82 | else: 83 | TickSimulator(self).start() 84 | 85 | cpm_fifo = deque([],60) 86 | cpm_dtc_fifo = deque([],60) 87 | while True: 88 | time.sleep(1) 89 | 90 | # Statistical correction of tube dead-time 91 | if gpio_available: 92 | deadtime = cfg.getfloat('geigercounter','tube_dead_time') 93 | count_dtc = int(self.count/(1-(self.count*deadtime))) 94 | else: 95 | count_dtc = self.count 96 | 97 | cpm_fifo.appendleft(self.count) 98 | cpm_dtc_fifo.appendleft(count_dtc) 99 | 100 | self.cpm = int(sum(cpm_fifo)*60.0/len(cpm_fifo)) 101 | self.cpm_dtc = int(sum(cpm_dtc_fifo)*60.0/len(cpm_dtc_fifo)) 102 | self.cps = self.count 103 | self.cps_dtc = count_dtc 104 | 105 | ratefactor = cfg.getfloat('geigercounter','tube_rate_factor') 106 | self.edr = round(self.cpm_dtc * ratefactor,2) 107 | 108 | self.totalcount_dtc += (count_dtc - self.count) 109 | 110 | self.count = 0 111 | 112 | log.debug(self.get_state()) 113 | 114 | def get_state(self): 115 | msg = { 116 | "type": "geigerjson", 117 | "node_uuid": cfg.get('node','uuid'), 118 | "timestamp": int(datetime.datetime.now().strftime("%s")), 119 | "geostamp": { 120 | "lat": cfg.getfloat('node','lat'), 121 | "lon": cfg.getfloat('node','lon'), 122 | "alt": cfg.getfloat('node','alt') 123 | }, 124 | "parameters": { 125 | "tube_id": cfg.get('geigercounter','tube_id'), 126 | "dead_time": cfg.getfloat('geigercounter','tube_dead_time'), 127 | "tube_factor": cfg.getfloat('geigercounter','tube_rate_factor'), 128 | "opmode": cfg.get('node', 'opmode'), 129 | "window": cfg.get('geigercounter', 'window') 130 | }, 131 | "data": { 132 | "source": cfg.get('geigercounter', 'source') if gpio_available else "sim", 133 | "cps": self.cps, 134 | "cps_dtc": self.cps_dtc, 135 | "cpm": self.cpm, 136 | "cpm_dtc": self.cpm_dtc, 137 | "totalcount": self.totalcount, 138 | "totalcount_dtc": self.totalcount_dtc, 139 | "edr": self.edr 140 | }, 141 | "annotation": "" 142 | 143 | } 144 | return msg 145 | -------------------------------------------------------------------------------- /software/pyGI/geigerlog.py: -------------------------------------------------------------------------------- 1 | import leveldb 2 | from datetime import datetime, timedelta 3 | import threading 4 | import os 5 | import sys 6 | import time 7 | import json 8 | import logging 9 | from collections import deque 10 | from configurator import cfg 11 | 12 | log = logging.getLogger(__name__) 13 | 14 | LOG_WRITE_RATE = 5 15 | MAX_ENTRY_DIST = 30 16 | 17 | script_dir = sys.path[0] 18 | 19 | def dt2unix(dt): 20 | return int(dt.strftime("%s")) 21 | 22 | def get_last_totalcount(): 23 | 24 | log.info("Getting last totalcount") 25 | db = leveldb.LevelDB(cfg.get('db','path')) 26 | now = dt2unix(datetime.now()) 27 | d = 1 28 | last_entries_keys = [] 29 | i = 0 30 | 31 | # Check for empty leveldb instance 32 | try: 33 | db.RangeIter(include_value=False).next() 34 | except StopIteration: 35 | log.info("Empty LevelDB") 36 | return (0,0) 37 | 38 | while not last_entries_keys: 39 | 40 | log.debug("Searching further (%d)..."%d) 41 | last_entries_keys = list(db.RangeIter(key_from=str(now-d),include_value=False)) 42 | 43 | d = d*2 44 | i = i+1 45 | 46 | last_key = last_entries_keys[-1] 47 | entry_json = db.Get(last_key) 48 | entry = json.loads(entry_json) 49 | return (entry['data']['totalcount'],entry['data']['totalcount_dtc']) 50 | 51 | def average_log_entries(entries,tube_rate_factor): 52 | result = [] 53 | previous_entry = None 54 | for entry in entries: 55 | if not previous_entry: 56 | previous_entry = entry 57 | result.append(entry) 58 | continue 59 | 60 | seconds = float(entry.get("timestamp",0)) - int(previous_entry.get("timestamp",0)) 61 | counts = float(entry['data'].get("totalcount_dtc",0)) - int(previous_entry['data'].get("totalcount_dtc",0)) 62 | 63 | if counts < 0: counts=0 64 | if seconds != 0: 65 | cps = counts/seconds 66 | cpm = cps * 60 67 | edr = round(cpm * tube_rate_factor,2) 68 | 69 | #entry["cps"] = int(cps) 70 | #entry["cpm"] = int(cpm) 71 | entry["data"]["edr"] = edr 72 | 73 | result.append(entry) 74 | previous_entry = entry 75 | return result 76 | 77 | class GeigerLog(threading.Thread): 78 | def __init__(self,geiger): 79 | self.db = leveldb.LevelDB(cfg.get('db','path')) 80 | self.db_annotation = leveldb.LevelDB(cfg.get('db','path')+".idx-annotation") 81 | self.geiger = geiger 82 | threading.Thread.__init__(self) 83 | self.daemon = True 84 | self.start() 85 | self.last_log = None 86 | 87 | def run(self): 88 | log.info("Starting geigerlog") 89 | avg_age = dt2unix(datetime.now() - timedelta(minutes=15)) 90 | avg_list = deque() 91 | entries_list = list(self.db.RangeIter(key_from=str(avg_age))) 92 | for e in entries_list: avg_list.append(json.loads(e[1])) 93 | while True: 94 | time.sleep(LOG_WRITE_RATE) 95 | avg_age = dt2unix(datetime.now() - timedelta(minutes=15)) 96 | if avg_list: 97 | while avg_list[0]["timestamp"] < avg_age: 98 | avg_list.popleft() 99 | 100 | state = self.geiger.get_state() 101 | avg_list.append(state) 102 | avg = round(sum([e["data"]["edr"] for e in avg_list])/len(avg_list),3) 103 | state["data"]["edr_avg"] = avg 104 | key = str(state["timestamp"]) 105 | value = json.dumps(state) 106 | self.db.Put(key, value) 107 | self.last_log = state 108 | log.debug("Logging: %s : %s"%(key,value)) 109 | log.debug(self.db.GetStats()) 110 | 111 | def get_log_entries_all(self,start,end): 112 | result = [] 113 | entries_list = list(self.db.RangeIter(key_from=str(start),fill_cache=True)) 114 | last_time = start 115 | for e in entries_list: 116 | entry = json.loads(e[1]) 117 | if int(entry["timestamp"])-last_time > MAX_ENTRY_DIST: 118 | insert_time = last_time + LOG_WRITE_RATE 119 | record_insert = dummy_entry(insert_time,entry['data']['totalcount'],entry['data']['totalcount_dtc']) 120 | while insert_time < int(entry["timestamp"]): 121 | result.append(record_insert.copy()) 122 | insert_time += 10 123 | record_insert["timestamp"]=insert_time 124 | last_time = int(entry["timestamp"]) 125 | result.append(entry) 126 | 127 | if result: 128 | last = result[-1] 129 | if end - int(last["timestamp"]) > MAX_ENTRY_DIST: 130 | insert_time = int(last["timestamp"]) + LOG_WRITE_RATE 131 | record_insert = dummy_entry(insert_time,entry['data']['totalcount'],entry['data']['totalcount_dtc']) 132 | while insert_time < end: 133 | result.append(record_insert.copy()) 134 | insert_time += 10 135 | record_insert["timestamp"]=insert_time 136 | return result 137 | 138 | def get_log_entries_sparse(self,start,end,amount): 139 | result = [] 140 | delta_total = end - start 141 | delta_step = delta_total / amount 142 | step = 0 143 | while True: 144 | t = start + delta_step * step 145 | if t > end: break 146 | if step >= 1: 147 | t_prev = start + delta_step * (step - 1) 148 | annotation_keys = list(self.db_annotation.RangeIter(key_from=str(t_prev),key_to=str(t),include_value=False)) 149 | if annotation_keys: 150 | for key in annotation_keys: 151 | result.append(json.loads(self.db.Get(key))) 152 | 153 | db_iter = self.db.RangeIter(key_from=str(t),fill_cache=True) 154 | try: 155 | (timestamp,entry_json) = db_iter.next() 156 | except StopIteration: 157 | break; 158 | 159 | entry = json.loads(entry_json) 160 | 161 | if int(timestamp)-t>MAX_ENTRY_DIST: 162 | entry=dummy_entry(t,entry['data']['totalcount'],entry['data']['totalcount_dtc']) 163 | 164 | 165 | if not result: 166 | result.append(entry) 167 | elif result[-1] != entry: 168 | result.append(entry) 169 | 170 | step += 1 171 | return average_log_entries(result,cfg.getfloat('geigercounter','tube_rate_factor')) 172 | 173 | 174 | def get_log_entries(self,start=None,end=None,age=None,amount=500): 175 | if end is None: 176 | end = dt2unix(datetime.now()) 177 | if age: 178 | start = end - age 179 | elif start is None: 180 | start = int(self.db.RangeIter(key_from="0",include_value=False).next()) 181 | 182 | log.info("Fetching %s log entries from %d to %s"%(str(amount),start,end)) 183 | 184 | if amount is None: 185 | return self.get_log_entries_all(start,end) 186 | else: 187 | return self.get_log_entries_sparse(start,end,amount) 188 | 189 | def set_annotation(self,ts,text): 190 | try: 191 | key = str(int(ts)) 192 | entry_json = self.db.Get(key) 193 | except KeyError: 194 | try: 195 | (key,entry_json) = self.db.RangeIter(key_from=str(int(ts))).next() 196 | except StopIteration: 197 | log.ERROR("Annotation timestamp out of log range: %s"%key) 198 | return 199 | entry = json.loads(entry_json) 200 | entry['annotation'] = text 201 | entry_json = json.dumps(entry) 202 | self.db.Put(key,entry_json) 203 | if text: 204 | self.db_annotation.Put(key,text) 205 | else: 206 | self.db_annotation.Delete(key) 207 | 208 | def dummy_entry(timestamp,total,total_dtc): 209 | msg = { 210 | "type": "geigerjson", 211 | "node_uuid": cfg.get('node','uuid'), 212 | "timestamp": timestamp, 213 | "data": { 214 | "source": "off", 215 | "cps": 0, 216 | "cps_dtc": 0, 217 | "cpm": 0, 218 | "cpm_dtc": 0, 219 | "totalcount": total, 220 | "totalcount_dtc": total_dtc, 221 | "edr": 0 222 | }, 223 | "annotation": "" 224 | } 225 | return msg 226 | 227 | 228 | if __name__ == "__main__": 229 | print get_last_totalcount() 230 | -------------------------------------------------------------------------------- /software/pyGI/geigerserver.py: -------------------------------------------------------------------------------- 1 | import bottle 2 | from gevent.pywsgi import WSGIServer 3 | from geventwebsocket import WebSocketError 4 | from geventwebsocket.handler import WebSocketHandler 5 | 6 | import json 7 | import logging 8 | import os 9 | import sys 10 | 11 | from configurator import cfg 12 | 13 | import geigercounter 14 | import geigerclient 15 | 16 | log=logging.getLogger(__name__) 17 | 18 | app = bottle.Bottle() 19 | script_dir = sys.path[0] 20 | public_dir = os.path.join(script_dir,"public") 21 | log_dir = os.path.join(script_dir,"log") 22 | 23 | geiger = None 24 | geigerlog = None 25 | clients_handler = None 26 | 27 | 28 | @app.route('/') 29 | def index(): 30 | return bottle.redirect('/webGI/index.html') 31 | 32 | 33 | @app.route('/favicon.ico') 34 | def favicon(): 35 | return bottle.static_file("favicon.ico", root=public_dir) 36 | 37 | 38 | @app.route('/webGI/:filename#.*#') 39 | def send_static(filename): 40 | log.debug("serving %s" % filename) 41 | return bottle.static_file(filename, root=public_dir) 42 | 43 | 44 | @app.route('/webGI/data/entropy.bin') 45 | def send_entropy(): 46 | log.debug("serving entropy file") 47 | return bottle.static_file("entropy.bin", root=log_dir, download=True) 48 | 49 | 50 | @app.route('/ws') 51 | def handle_ws(): 52 | env = bottle.request.environ 53 | wsock = env.get('wsgi.websocket') 54 | if not wsock: 55 | abort(400, 'Expected WebSocket request.') 56 | log.info("websocket opened (%s)"%wsock.path) 57 | client = geigerclient.WebSocketClientConnector(wsock) 58 | clients_handler.add(client) 59 | client.receive_commands(clients_handler) 60 | 61 | 62 | def start(g,gl): 63 | global geiger, geigerlog, clients_handler 64 | geiger = g 65 | geigerlog = gl 66 | clients_handler = geigerclient.ClientsHandler(geiger,geigerlog) 67 | 68 | ip = cfg.get('server','ip') 69 | port = cfg.getint('server','port') 70 | log.info("listening on %s:%d" % (ip, port)) 71 | 72 | server = WSGIServer((ip, port), app, 73 | handler_class=WebSocketHandler) 74 | server.serve_forever() 75 | -------------------------------------------------------------------------------- /software/pyGIserver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import logging 3 | from pyGI.configurator import cfg 4 | 5 | #setup logging 6 | log = logging.getLogger() 7 | log.setLevel(cfg.get('logging','level')) 8 | formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s: %(message)s') 9 | if cfg.getboolean('logging','write_file'): 10 | filehandler = logging.FileHandler(cfg.get('logging','filename')) 11 | filehandler.setFormatter(formatter) 12 | log.addHandler(filehandler) 13 | streamhandler = logging.StreamHandler() 14 | streamhandler.setFormatter(formatter) 15 | log.addHandler(streamhandler) 16 | 17 | if __name__ == "__main__": 18 | log.info("Starting pyGIserver") 19 | from pyGI import geigercounter,geigerserver,geigerlog 20 | try: 21 | #get last totalcount from db 22 | (last_total,last_total_dtc) = geigerlog.get_last_totalcount() 23 | log.info("Last total: %d, total_dtc: %d"%(last_total, last_total_dtc)) 24 | #start geigercounter 25 | geiger = geigercounter.Geigercounter(total=last_total, total_dtc=last_total_dtc) 26 | 27 | #start geigercounter logging 28 | geigerlog = geigerlog.GeigerLog(geiger) 29 | 30 | #start the bottle server stuff 31 | geigerserver.start(geiger,geigerlog) 32 | 33 | except KeyboardInterrupt: 34 | log.info("Stopping pyGIserver") 35 | --------------------------------------------------------------------------------