├── .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 | 
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 | 
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 | 
53 | #### History instrument panel
54 | 
55 | #### Ion trace visualizer
56 | 
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 | 
10 | ### History instrument panel
11 | 
12 | ### Ion trace visualizer
13 | 
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 | 
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/software/public/assets/img/orm_bw.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
175 |
--------------------------------------------------------------------------------
/software/public/assets/img/orm_col.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
191 |
--------------------------------------------------------------------------------
/software/public/assets/img/orm_new.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
--------------------------------------------------------------------------------