├── cmd ├── g5-calc-noise-go │ ├── g5-calc-noise-go │ └── main.go ├── g5-calc-calibration-go │ ├── .main.go.swp │ ├── g5-calc-calibration-go │ └── main.go └── g5-post-ns-go │ ├── test.json │ └── main.go ├── bin ├── g5-restart.sh ├── g5-battery.sh ├── logger-online.sh ├── g5-version.sh ├── g5-debug.sh ├── g5-insert.sh ├── g5-post-xdrip.sh ├── g5-reset.sh ├── g5-post-ns.sh ├── logger-common-funcs.sh ├── g5-stop.sh ├── upgrade-node.sh ├── g5-transmitter.sh ├── g5-start.sh ├── g5-noise.sh ├── calibrate.sh ├── logger-setup.sh ├── g5-calc-noise.sh └── g5-calc-calibration.sh ├── test ├── test-setup-dirs.sh ├── tdate.sh ├── test-pump-history-file.sh ├── validateJSON.sh ├── noise │ ├── unit-test-actual-noise.sh │ ├── no-noise-20.csv │ ├── hi.csv │ ├── unit-test-noise.sh │ ├── startup.csv │ ├── lo.csv │ └── noise-30.csv ├── newFirmware.sh ├── validBG.sh └── test-post-cgm-pill.sh ├── package.json ├── .gitignore ├── logger └── index.js ├── README.md └── xdrip-get-entries.sh /cmd/g5-calc-noise-go/g5-calc-noise-go: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdrip-js/Logger/HEAD/cmd/g5-calc-noise-go/g5-calc-noise-go -------------------------------------------------------------------------------- /cmd/g5-calc-calibration-go/.main.go.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdrip-js/Logger/HEAD/cmd/g5-calc-calibration-go/.main.go.swp -------------------------------------------------------------------------------- /bin/g5-restart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # convenience cmd line utility to do a start / stop backdated in one command 4 | cgm-stop 5 | cgm-start -m 120 6 | -------------------------------------------------------------------------------- /cmd/g5-calc-calibration-go/g5-calc-calibration-go: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdrip-js/Logger/HEAD/cmd/g5-calc-calibration-go/g5-calc-calibration-go -------------------------------------------------------------------------------- /cmd/g5-post-ns-go/test.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "enteredBy": "OpenAPS", 4 | "reason": "testing", 5 | "eventType": "Note", 6 | "notes": "Testing Yo a note", 7 | "glucoseType": "Finger", 8 | "units": "mg/dl" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /test/test-setup-dirs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function check_dirs() 4 | { 5 | LDIR=~/myopenaps/monitor/xdripjs5 6 | OLD_LDIR=~/myopenaps/monitor/logger5 7 | 8 | if [ ! -d ${LDIR} ]; then 9 | if [ -d ${OLD_LDIR} ]; then 10 | mv ${OLD_LDIR} ${LDIR} 11 | fi 12 | fi 13 | mkdir -p ${LDIR} 14 | mkdir -p ${LDIR}/old-calibrations 15 | } 16 | 17 | check_dirs 18 | -------------------------------------------------------------------------------- /bin/g5-battery.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # optional parameter $1 to specify "refresh" then it queues a Battery refresh ctl message to tx 4 | refresh=$1 5 | 6 | file="${HOME}/myopenaps/monitor/xdripjs/cgm-battery.json" 7 | if [ -n "$refresh" ]; then 8 | echo "Queueing Battery Status Refresh message for next Tx transmission (5 to 10 minutes)" 9 | touch -d "13 hours ago" $file 10 | else 11 | cat $file 12 | echo 13 | fi 14 | -------------------------------------------------------------------------------- /test/tdate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | timestamp="2019-07-10T19:25:36-04:00" 4 | timestamp=$(date)#"2019-07-10T19:25:36-04:00" 5 | 6 | timestamp=$(date +'%Y-%m-%dT%H:%M:%S.%3N') 7 | 8 | 9 | epochdate=`date --date="$timestamp" +"%s"` 10 | 11 | timestamp2=$(date -u -d @$epochdate +'%Y-%m-%dT%H:%M:%S.%3NZ') 12 | echo timestamp=$timestamp 13 | echo epochdate=$epochdate 14 | echo timestamp2=$timestamp2 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /bin/logger-online.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function check_ip { 4 | PUBLIC_IP=$(curl --compressed -4 -s -m 15 checkip.amazonaws.com | awk -F , '{print $NF}' | egrep "^[12]*[0-9]*[0-9]\.[12]*[0-9]*[0-9]\.[12]*[0-9]*[0-9]\.[12]*[0-9]*[0-9]$") 5 | if [[ -z $PUBLIC_IP ]]; then 6 | echo not found 7 | return 1 8 | else 9 | echo $PUBLIC_IP 10 | fi 11 | } 12 | 13 | if check_ip; then 14 | exit 0 15 | else 16 | exit 1 17 | fi 18 | -------------------------------------------------------------------------------- /bin/g5-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MESSAGE="${HOME}/myopenaps/monitor/xdripjs/cgm-version.json" 4 | epochdate=$(date +'%s%3N') 5 | 6 | echo "Requesting the Dexcom Transmitter version number" 7 | echo "Monitor the Logger logfile to view the version number" 8 | 9 | echo "[{\"date\":\"${epochdate}\",\"type\":\"VersionRequest\"}]" > $MESSAGE 10 | cat $MESSAGE 11 | echo 12 | echo "VersionRequest message sent to Logger" 13 | echo "Monitor the Logger logfile to view the version number" 14 | -------------------------------------------------------------------------------- /bin/g5-debug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -v 3 | :&&:&&:&&:&& df -h | grep -v tmpfs 4 | :&&:&&:&&:&& cgm-battery 5 | :&&:&&:&&:&& bluetoothd -v 6 | :&&:&&:&&:&& cgm-noise 2 7 | :&&:&&:&&:&& cat ~/myopenaps/xdripjs.json 8 | :&&:&&:&&:&& bt-device -l 9 | :&&:&&:&&:&& crontab -l | grep Logger 10 | :&&:&&:&&:&& tail -8 ~/myopenaps/monitor/xdripjs/calibrations.csv 11 | :&&:&&:&&:&& cat ~/myopenaps/monitor/xdripjs/calibration-linear.json 12 | :&&:&&:&&:&& tail -12 /var/log/openaps/logger-loop.log 13 | echo 14 | 15 | -------------------------------------------------------------------------------- /test/test-pump-history-file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function testpumphistory() 4 | { 5 | 6 | if [ -e "$HOME/myopenaps/monitor/pumphistory-zoned.json" ]; then 7 | echo found first file 8 | else 9 | if [ -e "$HOME/myopenaps/monitor/pumphistory-24h-zoned.json" ]; then 10 | echo found second file 11 | fi 12 | fi 13 | 14 | historyFile="$HOME/myopenaps/monitor/pumphistory-24h-zoned.json" 15 | if [ ! -e "$historyFile" ]; then 16 | echo did not find $historyFile 17 | else 18 | echo found $historyFile 19 | fi 20 | 21 | } 22 | 23 | testpumphistory 24 | -------------------------------------------------------------------------------- /bin/g5-insert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MESSAGE="${HOME}/myopenaps/monitor/xdripjs/cgm-reset.json" 4 | 5 | LDIR="${HOME}/myopenaps/monitor/xdripjs" 6 | 7 | function ClearCalibrationInput() 8 | { 9 | if [ -e ${LDIR}/calibrations.csv ]; then 10 | cp ${LDIR}/calibrations.csv "${LDIR}/old-calibrations/calibrations.csv.$(date +%Y%m%d-%H%M%S)" 11 | rm ${LDIR}/calibrations.csv 12 | fi 13 | } 14 | 15 | ClearCalibrationInput 16 | echo "Logger calibration files cleared." 17 | echo "Wait at least 15 minutes and then calibrate" 18 | #TODO - add NS Sensor Insert record 19 | -------------------------------------------------------------------------------- /bin/g5-post-xdrip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # exit codes 4 | # -1 ==> $1 file doesn't exist 5 | # 0 ==> success 6 | # other ==> curl_status 7 | 8 | source ~/.bash_profile 9 | 10 | export API_SECRET 11 | 12 | ns_url="http://127.0.0.1:5000" 13 | ns_secret="${API_SECRET}" 14 | 15 | curl_status=-1 16 | 17 | if [ -e $1 ]; then 18 | curl --compressed -f -m 30 -s -X POST -d @$1 \ 19 | -H "API-SECRET: $ns_secret" \ 20 | -H "Content-Type: application/json" \ 21 | "${ns_url}/api/v1/entries" 22 | curl_status=$? 23 | fi 24 | 25 | #echo "in post xdrip entry: status=${curl_status} " 26 | 27 | exit $curl_status 28 | -------------------------------------------------------------------------------- /bin/g5-reset.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MESSAGE="${HOME}/myopenaps/monitor/xdripjs/cgm-reset.json" 4 | epochdate=$(date +'%s%3N') 5 | 6 | echo "Running this command will instruct Logger to reset the Dexcom Transmitter!" 7 | echo " Your current session will be lost and will have to be restarted using cgm-start" 8 | 9 | read -p "Are you sure? (y/n)" -n 1 -r 10 | echo 11 | if [[ $REPLY =~ ^[Yy]$ ]] 12 | then 13 | echo "[{\"date\":\"${epochdate}\",\"type\":\"ResetTx\"}]" > $MESSAGE 14 | cat $MESSAGE 15 | echo 16 | echo "ResetTx message sent to Logger" 17 | echo "Wait 5 to 10 minutes for message to be processed" 18 | else 19 | echo "ResetTx message not sent to Logger" 20 | fi 21 | -------------------------------------------------------------------------------- /bin/g5-post-ns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ~/.bash_profile 4 | 5 | INPUT=${1:-""} 6 | NSTYPE=${2:-"entries"} 7 | 8 | 9 | export NIGHTSCOUT_HOST 10 | export API_SECRET 11 | 12 | ns_url="${NIGHTSCOUT_HOST}" 13 | ns_secret="${API_SECRET}" 14 | 15 | # exit codes 16 | # -1 ==> INPUT file doesn't exist 17 | # 0 ==> success 18 | # other ==> curl_status 19 | 20 | curl_status=-1 21 | 22 | if [ -e $INPUT ]; then 23 | curl --retry 4 --compressed -f -m 30 -s -X POST -d @$INPUT \ 24 | -H "API-SECRET: $ns_secret" \ 25 | -H "Content-Type: application/json" \ 26 | "${ns_url}/api/v1/${NSTYPE}.json" 27 | curl_status=$? 28 | fi 29 | 30 | #echo "curl status from cgm-post-ns is $curl_status" 31 | exit $curl_status 32 | -------------------------------------------------------------------------------- /bin/logger-common-funcs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/echo This file should be source'd from another script, not run directly: 2 | # 3 | # Common functions for shell script components of Logger. 4 | 5 | LDIR="${HOME}/myopenaps/monitor/xdripjs" 6 | 7 | function newFirmware() 8 | { 9 | local version=$1 10 | case $version in 11 | 1.6.5.27 | 2.*) 12 | echo true 13 | ;; 14 | *) 15 | echo false 16 | ;; 17 | esac 18 | } 19 | 20 | function txVersion() 21 | { 22 | local tx_version="" 23 | if [ -e "${LDIR}/tx-version.json" ]; then 24 | tx_version=$(cat ${LDIR}/tx-version.json | jq -M '.firmwareVersion') 25 | tx_version="${tx_version%\"}" 26 | tx_version="${tx_version#\"}" 27 | fi 28 | 29 | echo $tx_version 30 | } 31 | 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Logger", 3 | "version": "1.2.4", 4 | "description": "Logger xdrip openaps", 5 | "main": "logger/index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "make test", 9 | "setup": "./bin/logger-setup.sh", 10 | "global-install": "rm -rf ./node-modules/xdrip-js && ./bin/upgrade-node.sh && npm install && ./bin/logger-setup.sh" 11 | }, 12 | "dependencies": { 13 | "xdrip-js": "xdrip-js/xdrip-js#v2.1.8" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/xdrip-js/Logger.git" 18 | }, 19 | "homepage": "https://github.com/xdrip-js/Logger", 20 | "config": { 21 | "blanket": { 22 | "pattern": [ 23 | "bin" 24 | ], 25 | "data-cover-never": [ 26 | "node_modules" 27 | ] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bin/g5-stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | common_funcs="/root/src/Logger/bin/logger-common-funcs.sh" 4 | if [ ! -e $common_funcs ]; then 5 | echo "ERROR: Failed to run logger-common-funcs.sh. Is Logger correctly installed?" 6 | exit 1 7 | fi 8 | source $common_funcs 9 | 10 | # Except for new firmware transmitters, always do 120 minutes ago 11 | # for sensor stop message. Allows time travel for subsequent start. 12 | 13 | version=$(txVersion) 14 | if [ $(newFirmware $version) == "true" ]; then 15 | # time travel not allowed for new firmware tx versions 16 | minutesago=0 17 | else 18 | # always time travel in this case 19 | minutesago=120 20 | fi 21 | 22 | MESSAGE="${HOME}/myopenaps/monitor/xdripjs/cgm-stop.json" 23 | 24 | epochdate=$(date +'%s%3N' -d "$minutesago minutes ago") 25 | 26 | echo "[{\"date\":\"${epochdate}\",\"type\":\"StopSensor\"}]" > $MESSAGE 27 | cat $MESSAGE 28 | -------------------------------------------------------------------------------- /test/validateJSON.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function checkJSON() 4 | { 5 | local json_string=$1 6 | jqType=$(jq type <<< "$json_string") 7 | if [[ "$jqType" == *"array"* ]]; then 8 | echo "success, jqType=$jqType, string= $json_string" 9 | else 10 | echo "failure, jqType=$jqType, string= $json_string" 11 | fi 12 | } 13 | 14 | checkJSON "[{\"date\":1563014402000,\"type\":\"CalibrateSensor\",\"glucose\":85}]" 15 | checkJSON "[{\"date\"s:1563014402000,\"type\":\"CalibrateSensor\",\"glucose\":85}]" 16 | checkJSON "" 17 | checkJSON 18 | checkJSON "0" 19 | checkJSON "[]" 20 | checkJSON "[date:4]" 21 | 22 | exit 23 | 24 | timestamp="2019-07-10T19:25:36-04:00" 25 | 26 | 27 | epochdate=`date --date="$timestamp" +"%s"` 28 | 29 | createdAt=$(date -d @$epochdate +'%Y-%m-%dT%H:%M:%S.%3NZ') 30 | echo timestamp=$timestamp 31 | echo epochdate=$epochdate 32 | echo createdAt=$createdAt 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/noise/unit-test-actual-noise.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | inputFile=${1:-"/var/log/openaps/cgm.csv"} 3 | calcProg=${2:-"g5-calc-noise"} 4 | tempFile=/tmp/noise-test-subset 5 | records41Minutes=8 6 | 7 | numRecords=`wc -l $inputFile | cut -d' ' -f1` 8 | if [ "$inputFile" == "/var/log/openaps/cgm.csv" ]; then 9 | numRecords=$(bc <<< "$numRecords - 1") 10 | fi 11 | 12 | if [ $(bc <<< "$numRecords > 50") -eq 1 ]; then 13 | echo "numRecords of $numRecords > 50 so limiting to 50" 14 | tail -50 $inputFile > "/tmp/cut-cgm50.csv" 15 | inputFile="/tmp/cut-cgm50.csv" 16 | numRecords=`wc -l $inputFile | cut -d' ' -f1` 17 | else 18 | echo "numRecords=$numRecords" 19 | fi 20 | 21 | for (( i=2; i<=$(($numRecords - $records41Minutes + 1)); i++ )) 22 | do 23 | tail -n+$i $inputFile | head -$records41Minutes | cut -d',' -f1,3,4 > ${tempFile}.csv 24 | echo -n `tail -1 ${tempFile}.csv` 25 | echo -n " " 26 | $calcProg ${tempFile}.csv ${tempFile}.json 27 | #cat ${tempFile}.csv 28 | done 29 | -------------------------------------------------------------------------------- /test/newFirmware.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | common_funcs="/root/src/Logger/bin/logger-common-funcs.sh" 4 | if [ ! -e $common_funcs ]; then 5 | echo "ERROR: Failed to run logger-common-funcs.sh. Is Logger correctly installed?" 6 | exit 1 7 | fi 8 | source $common_funcs 9 | 10 | #KNOWN_G5_FIRMWARES = ("1.0.0.13", "1.0.0.17", "1.0.4.10", "1.0.4.12"); 11 | #KNOWN_G6_FIRMWARES = ("1.6.5.23", "1.6.5.25", "1.6.5.27"); 12 | #KNOWN_G6_REV2_FIRMWARES = ("2.18.2.67", "2.18.2.88"); 13 | #KNOWN_TIME_TRAVEL_TESTED = ("1.6.5.25"); 14 | 15 | function testVersion() 16 | { 17 | if [ "$(newFirmware $version)" == "true" ]; then 18 | echo "'$version' is new firmware" 19 | else 20 | echo "'$version' is not new firmware" 21 | fi 22 | } 23 | 24 | echo "real version test" 25 | version=$(txVersion)&& testVersion 26 | echo "fake version tests" 27 | version="1.6.5.27" && testVersion 28 | version="2.18.2.67" && testVersion 29 | version="2.18.2.88" && testVersion 30 | version="1.0.0.17" && testVersion 31 | version="" && testVersion 32 | version="1.6.5.25" && testVersion 33 | 34 | 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | /*.json 61 | /calibrations.csv.* 62 | /calibrations.json.* 63 | /calibration-linear.json.* 64 | /*.dat 65 | /*.csv 66 | -------------------------------------------------------------------------------- /cmd/g5-post-ns-go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "github.com/efidoman/xdrip-js-logger/logger" 8 | "io/ioutil" 9 | "log" 10 | // "net/http" 11 | "os" 12 | // "time" 13 | ) 14 | 15 | // 16 | // exit codes 17 | // -1 ==> INPUT file doesn't exist 18 | // 0 ==> success 19 | // -2 ==> err on http request 20 | 21 | var curlStatus int = -1 22 | 23 | func usage() { 24 | fmt.Fprintf(os.Stderr, "usage: %s inputjsonfile type\n", os.Args[0]) 25 | fmt.Fprintf(os.Stderr, " inputjsonfile = Nightscout json record file\n") 26 | fmt.Fprintf(os.Stderr, " type = Nightscout record type, default is \"entries\"\n") 27 | flag.PrintDefaults() 28 | os.Exit(curlStatus) 29 | } 30 | 31 | func main() { 32 | 33 | flag.Parse() 34 | flag.Usage = usage 35 | 36 | var nsUrl string = os.Getenv("NIGHTSCOUT_HOST") 37 | var nsSecret string = os.Getenv("API_SECRET") 38 | 39 | //fmt.Fprintf(os.Stderr, "nsUrl=%s, nsSecret=%s\n", nsUrl, nsSecret) 40 | 41 | if flag.NArg() < 2 { 42 | usage() 43 | } 44 | //fmt.Fprintf(os.Stderr, "arg0=%s\n", flag.Arg(0)) 45 | //fmt.Fprintf(os.Stderr, "arg1=%s\n", flag.Arg(1)) 46 | 47 | jsonFile := flag.Arg(0) 48 | nsType := flag.Arg(1) 49 | err, body := PostNightscoutRecord(jsonFile, nsType, nsUrl, nsSecret) 50 | if err != nil { 51 | log.Fatal(err) 52 | curlStatus = -2 53 | } 54 | fmt.Println(body) 55 | os.Exit(curlStatus) 56 | } 57 | -------------------------------------------------------------------------------- /bin/upgrade-node.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script will upgrade node to v8 if not already at least on that version 4 | # 5 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 6 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 8 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 9 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 10 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 11 | # THE SOFTWARE. 12 | 13 | die() { 14 | echo "$@" 15 | exit 1 16 | } 17 | 18 | #install/upgrade to latest node 8 if neither node 8 nor node 10+ LTS are installed 19 | if ! nodejs --version | grep -e 'v8\.' -e 'v1[02468]\.' &> /dev/null ; then 20 | echo "Node version not at v8 - upgrading ..." 21 | if ! uname -a | grep -e 'armv6' &> /dev/null ; then 22 | sudo bash -c "curl -sL https://deb.nodesource.com/setup_8.x | bash -" || die "Couldn't setup node 8" 23 | sudo apt-get install -y nodejs=8.* || die "Couldn't install nodejs" 24 | else 25 | sudo apt-get install -y nodejs npm || die "Couldn't install nodejs and npm" 26 | npm install npm@latest -g || die "Couldn't update npm" 27 | fi 28 | else 29 | echo "Node version already at v8 - good to go" 30 | fi 31 | -------------------------------------------------------------------------------- /bin/g5-transmitter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function usage 4 | { 5 | echo "Usage:" 6 | echo "cgm-transmitter [transmitter_id]" 7 | echo 8 | exit 9 | } 10 | 11 | function generate_config 12 | { 13 | echo "{\"transmitter_id\":\"${txId}\",\"sensor_code\":\"\",\"mode\":\"not-expired\",\"pump_units\":\"mg/dl\",\"fake_meter_id\":\"000000\",\"alternate_bluetooth_channel\":true}" | jq . > $config 14 | } 15 | 16 | numArgs=$# 17 | if [ $(bc <<< "$numArgs < 1") -eq 1 ] || [ $(bc <<< "$numArgs > 1") -eq 1 ]; then 18 | usage 19 | exit 20 | fi 21 | 22 | txId=$1 23 | 24 | #validate transmitter id 25 | if [ ${#txId} -ne 6 ]; then 26 | echo "Invalid transmitter id; should be 6 characters long" 27 | exit 28 | elif [ ${txId:0:1} == 8 ]; then 29 | echo "setting G6 transmitter: $txId" 30 | else 31 | echo "setting G5 transmitter: $txId" 32 | fi 33 | 34 | #Update config file 35 | config="/root/myopenaps/xdripjs.json" 36 | if [ -e "$config" ]; then 37 | echo "config found, updating xdripjs.json" 38 | tmp=$(mktemp) 39 | jq --arg txId "$txId" '.transmitter_id = $txId' "$config" > "$tmp" && mv "$tmp" "$config" 40 | else 41 | echo "Config file does not exist, generating xdripjs.json" 42 | generate_config 43 | fi 44 | 45 | #Post message 46 | MESSAGE="${HOME}/myopenaps/monitor/xdripjs/cgm-transmitter.json" 47 | epochdate=$(date +'%s%3N') 48 | echo "[{\"date\":\"${epochdate}\",\"type\":\"New Transmitter\",\"txId\":\"${txId}\"}]" > $MESSAGE 49 | cat $MESSAGE 50 | -------------------------------------------------------------------------------- /bin/g5-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function usage 4 | { 5 | echo "Usage:" 6 | echo "cgm-start [minutes-ago]" 7 | echo "cgm-start [-m minutes-ago] [-c g6_sensor_code] [-t transmitter_id]" 8 | echo 9 | exit 10 | } 11 | 12 | numArgs=$# 13 | if [ $(bc <<< "$numArgs > 1") -eq 1 ]; then 14 | # use -m or -c to specify minutes ago and/or g6 code 15 | while test $# != 0 16 | do 17 | case "$1" in 18 | -m|--minutes_ago) minutes_ago=$2; shift ;; 19 | -c|--code) code=$2; shift ;; 20 | -t|--transmitter) txId=$2; shift ;; 21 | -h|--help) usage; ;; 22 | *) usage ;; 23 | esac 24 | shift 25 | done 26 | elif [ $(bc <<< "$numArgs > 0") -eq 1 ]; then 27 | # optional parameter $1 to specify how many minutes ago for sensor insert/start 28 | minutes_ago=$1 29 | fi 30 | 31 | #update config 32 | if [ -n $txId ]; then 33 | if [ "$txId" != "null" -a "$txId" != "" ]; then 34 | cgm-transmitter "$txId" 35 | fi 36 | fi 37 | if [ -n $code ]; then 38 | if [ "$code" != "null" -a "$code" != "" ]; then 39 | config="/root/myopenaps/xdripjs.json" 40 | if [ -e "$config" ]; then 41 | tmp=$(mktemp) 42 | jq --arg code "$code" '.sensor_code = $code' "$config" > "$tmp" && mv "$tmp" "$config" 43 | fi 44 | fi 45 | fi 46 | 47 | MESSAGE="${HOME}/myopenaps/monitor/xdripjs/cgm-start.json" 48 | if [ -n "$minutes_ago" ]; then 49 | epochdate=$(date +'%s%3N' -d "$minutes_ago minutes ago") 50 | else 51 | epochdate=$(date +'%s%3N') 52 | fi 53 | 54 | echo "[{\"date\":\"${epochdate}\",\"type\":\"StartSensor\",\"sensorSerialCode\":\"${code}\"}]" > $MESSAGE 55 | cat $MESSAGE 56 | -------------------------------------------------------------------------------- /test/validBG.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function log() 4 | { 5 | echo $1 6 | } 7 | 8 | 9 | function validNumber() 10 | { 11 | local num=$1 12 | case ${num#[-+]} in 13 | *[!0-9.]* | '') echo false ;; 14 | * ) echo true ;; 15 | esac 16 | } 17 | 18 | 19 | function validBG() 20 | { 21 | local bg=$1 22 | local valid="false" 23 | 24 | if [ "$(validNumber $bg)" == "true" ]; then 25 | if [ $(bc -l <<< "$bg >= 20") -eq 1 -a $(bc -l <<< "$bg < 500") -eq 1 ]; then 26 | valid="true" 27 | fi 28 | fi 29 | 30 | echo $valid 31 | } 32 | 33 | LDIR=~/myopenaps/monitor/xdripjs 34 | #unfiltered=110 35 | filtered=111 36 | raw=$unfiltered 37 | epochdate=$(date +'%s') 38 | 39 | 40 | 41 | #unfiltered=$(cat ${LDIR}/entry.json | jq -M '.[0].uknfiltered') 42 | #unfiltered=$(bc -l <<< "scale=0; $unfiltered / 1000") 43 | 44 | 45 | b=111 46 | if [ "$(validBG $b)" == "true" ]; then 47 | echo "$b=valid" 48 | else 49 | echo "$b=invalid" 50 | fi 51 | 52 | b=611 53 | if [ "$(validBG $b)" == "true" ]; then 54 | echo "$b=valid" 55 | else 56 | echo "$b=invalid" 57 | fi 58 | 59 | #echo "unfiltered=$unfiltered" 60 | 61 | echo "validBG undefined($fkiltered)=$(validBG $fkiltered)" 62 | 63 | filtered="what" 64 | echo "validBG $filtered=$(validBG $filtered)" 65 | 66 | filtered=111 67 | echo "validBG $filtered=$(validBG $filtered)" 68 | 69 | filtered=511 70 | echo "validBG $filtered=$(validBG $filtered)" 71 | 72 | filtered=111.000 73 | echo "validBG $filtered=$(validBG $filtered)" 74 | 75 | filtered= 76 | echo "validBG $filtered=$(validBG $filtered)" 77 | 78 | filtered=11 79 | echo "validBG $filtered=$(validBG $filtered)" 80 | -------------------------------------------------------------------------------- /bin/g5-noise.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | maxRecords=${1:-25} 4 | inputFile=${2:-"/var/log/openaps/cgm.csv"} 5 | 6 | n=0 7 | 8 | records=$(cat $inputFile | wc -l) 9 | 10 | if [ $(bc <<< "$records - 1 < $maxRecords") -eq 1 ]; then 11 | n=$((records-1)) 12 | echo "records of $records -1 < $maxRecords" 13 | maxRecords=$n 14 | fi 15 | 16 | 17 | 18 | if [ -e $inputFile ]; then 19 | arrdate=( $(tail -$maxRecords $inputFile | cut -d ',' -f1 ) ) 20 | unfiltered=( $(tail -$maxRecords $inputFile | cut -d ',' -f3 ) ) 21 | filtered=( $(tail -$maxRecords $inputFile | cut -d ',' -f4 ) ) 22 | arrlsrbg=( $(tail -$maxRecords $inputFile | cut -d ',' -f6 ) ) 23 | arrtxbg=( $(tail -$maxRecords $inputFile | cut -d ',' -f7 ) ) 24 | arrnoise=( $(tail -$maxRecords $inputFile | cut -d ',' -f14 ) ) 25 | arrnoisesend=( $(tail -$maxRecords $inputFile | cut -d ',' -f15 ) ) 26 | 27 | n=${#arrdate[@]} 28 | fi 29 | 30 | 31 | 32 | for (( i=0; i<$n; i++ )) 33 | do 34 | arrdate[$i]=$(date -d @${arrdate[$i]} +%Y%m%d-%H:%M) 35 | noisetext[$i]="Error" 36 | if [[ "${arrnoisesend[$i]}" == "1" ]]; then 37 | noisetext[$i]="Clean" 38 | elif [[ "${arrnoisesend[$i]}" == "2" ]]; then 39 | noisetext[$i]="Light" 40 | elif [[ "${arrnoisesend[$i]}" == "3" ]]; then 41 | noisetext[$i]="Medium" 42 | elif [[ "${arrnoisesend[$i]}" == "4" ]]; then 43 | noisetext[$i]="Heavy" 44 | fi 45 | 46 | # unfiltered[$i]=$(bc <<< "${unfiltered[$i]} / 1000") 47 | # filtered[$i]=$(bc <<< "${filtered[$i]} / 1000") 48 | done 49 | 50 | echo "Date, unfilt, filt, LSRbg, Txbg, noise, noisesend, noisetext" 51 | for (( i=0; i<$n; i++ )) 52 | do 53 | # echo "${arrdate[$i]}" 54 | # noisetimes100=$(bc <<< "${arrnoise[$i]} * 100") 55 | echo "${arrdate[$i]}, ${unfiltered[$i]}, ${filtered[$i]}, ${arrlsrbg[$i]}, ${arrtxbg[$i]}, ${arrnoise[$i]}, ${arrnoisesend[$i]}, ${noisetext[$i]}" 56 | done 57 | -------------------------------------------------------------------------------- /bin/calibrate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Takes in BG calibration as argument #1 and after some boundary checking 4 | # it puts it in to ~/openaps/monitor/xdripjs/calibration.json 5 | # This way any apps can put a calibration bg record in that 6 | # file and Logger will pick it up and use it for calibration. 7 | 8 | bg=${1:-"null"} # arg 1 is meter bg value 9 | UNITS=${2:-"mg/dl"} # arg 2 if "mmol" then bg in mmol 10 | TEST=${3:-""} # arg 3 if "test" then test mode 11 | 12 | calibrationFile="${HOME}/myopenaps/monitor/xdripjs/calibration.json" 13 | stagingFile1=$(mktemp) 14 | stagingFile2=$(mktemp) 15 | #dateString=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") 16 | 17 | epochdate=$(date +'%s') 18 | # doing it this way for consistency - Logger will convert back to seconds from ms 19 | epochdate=$(($epochdate * 1000)) # xdrip-js requires milliseconds epoch 20 | 21 | LOW=40 22 | HIGH=400 23 | 24 | # ability to test without causing bg Check treatment to be used 25 | if [ "$TEST" == "test" ]; then 26 | tmp=$(mktemp) 27 | calibrationFile=$(mktemp) 28 | fi 29 | 30 | if [ "$UNITS" == "mmol" ]; then 31 | bg=$(bc <<< "($bg *18)/1") 32 | echo "converted OpenAPS meterbg from $1 mmol value to $bg mg/dl" 33 | fi 34 | 35 | if [ "$bg" == "null" ]; then 36 | echo "Error - Missing required argument 1 meterBG value" 37 | echo "Usage: calibrate MeterBG" 38 | exit 39 | fi 40 | 41 | if [ $(bc <<< "$bg >= $LOW") -eq 1 -a $(bc <<< "$bg <= $HIGH") -eq 1 ]; then 42 | echo "[{\"date\":$epochdate,\"type\":\"CalibrateSensor\",\"glucose\":$bg}]" > $stagingFile2 43 | if [ -e $calibrationFile ]; then 44 | cp $calibrationFile $stagingFile1 45 | jq -c -s add $stagingFile2 $stagingFile1 > $calibrationFile 46 | else 47 | cp $stagingFile2 $calibrationFile 48 | fi 49 | echo "calibration treatment posted to $calibrationFile - record is below" 50 | cat $calibrationFile 51 | else 52 | echo "Error - bg of $bg $UNITS is out of range ($LOW-$HIGH $UNITS) for calibration and cannot be used" 53 | fi 54 | 55 | 56 | -------------------------------------------------------------------------------- /test/noise/no-noise-20.csv: -------------------------------------------------------------------------------- 1 | epochdate,datetime,unfiltered,filtered,direction,calibratedBG-lsr,cgm-glucose,meterbg,slope,yIntercept,slopeError,yError,rSquared,Noise,NoiseSend,mode,noise*100,sensitivity,rssi 2 | 1561923663,2019-06-30 13:41,101,103,Flat,99,99,,1.07,-5.06,0,1,0.99919,0.92,4,native-calibrates-lsr,92.00,0.75,-88 3 | 1561923962,2019-06-30 13:46,102,103,Flat,100,100,,1.07,-5.06,0,1,0.99919,0.92,4,native-calibrates-lsr,92.00,0.75,-85 4 | 1561924261,2019-06-30 13:51,95,101,Flat,93,91,,1.07,-5.06,0,1,0.99919,0.87,4,native-calibrates-lsr,87.00,0.75,-80 5 | 1561924563,2019-06-30 13:56,91,99,FortyFiveDown,89,87,,1.07,-5.06,0,1,0.99919,0.87,4,native-calibrates-lsr,87.00,0.75,-78 6 | 1561924861,2019-06-30 14:01,91,95,Flat,89,87,,1.07,-5.06,0,1,0.99919,0.91,4,native-calibrates-lsr,91.00,0.75,-85 7 | 1561925161,2019-06-30 14:06,88,91,Flat,86,85,,1.07,-5.06,0,1,0.99919,0.88,4,native-calibrates-lsr,88.00,0.75,-85 8 | 1561925461,2019-06-30 14:11,88,89,Flat,86,85,,1.07,-5.06,0,1,0.99919,0.86,4,native-calibrates-lsr,86.00,0.75,-90 9 | 1561925761,2019-06-30 14:16,88,88,Flat,86,87,,1.07,-5.06,0,1,0.99919,0.91,4,native-calibrates-lsr,91.00,0.75,-81 10 | 1561926061,2019-06-30 14:21,93,88,Flat,91,93,,1.07,-5.06,0,1,0.99919,0.91,4,native-calibrates-lsr,91.00,0.75,-92 11 | 1561926361,2019-06-30 14:26,93,90,Flat,91,93,,1.07,-5.06,0,1,0.99919,0.89,4,native-calibrates-lsr,89.00,0.75,-84 12 | 1561926662,2019-06-30 14:31,95,92,Flat,93,95,,1.07,-5.06,0,1,0.99919,0.93,4,native-calibrates-lsr,93.00,0.75,-68 13 | 1561926963,2019-06-30 14:36,97,94,Flat,95,97,,1.07,-5.06,0,1,0.99919,0.93,4,native-calibrates-lsr,93.00,0.75,-65 14 | 1561927261,2019-06-30 14:41,100,96,Flat,98,100,,1.07,-5.06,0,1,0.99919,0.93,4,native-calibrates-lsr,93.00,0.75,-62 15 | 1561927561,2019-06-30 14:46,107,99,FortyFiveUp,104,108,,1.07,-5.06,0,1,0.99919,0.93,4,native-calibrates-lsr,93.00,0.75,-67 16 | 1561927861,2019-06-30 14:51,111,103,FortyFiveUp,108,113,,1.07,-5.06,0,1,0.99919,0.92,4,native-calibrates-lsr,92.00,0.75,-61 17 | 1561928162,2019-06-30 14:56,113,108,Flat,110,114,,1.07,-5.06,0,1,0.99919,0.92,4,native-calibrates-lsr,92.00,0.75,-63 18 | 1561928461,2019-06-30 15:01,119,112,Flat,115,120,,1.07,-5.06,0,1,0.99919,0.92,4,native-calibrates-lsr,92.00,0.75,-60 19 | 1561928763,2019-06-30 15:06,131,118,FortyFiveUp,127,133,,1.07,-5.06,0,1,0.99919,0.91,4,native-calibrates-lsr,91.00,0.75,-68 20 | 1561929063,2019-06-30 15:11,148,125,DoubleUp,143,153,,1.07,-5.06,0,1,0.99919,0.89,4,native-calibrates-lsr,89.00,0.75,-61 21 | -------------------------------------------------------------------------------- /test/test-post-cgm-pill.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | jstr="" 4 | 5 | # This func takes an arg list of value name pairs creating a simple json string 6 | # What's different about this vs jq is that this function will ignore 7 | # any value/name pair where the variable value name is null or blank 8 | # it also automatically handles quotes around variable values with strings 9 | # and doesn't include quotes for those without strings 10 | 11 | function build_json() { 12 | local __result="{" 13 | 14 | args=("$@") 15 | for (( i=0; i < ${#}; i+=2 )) 16 | do 17 | local __key=${args[$i]} 18 | local __value=${args[$i+1]} 19 | #echo "key=$__key, value=$__value" 20 | local __len=${#__value} 21 | if [ $__len -gt 0 ]; then 22 | if [ $(echo "$__value" | grep -cE "^\-?([0-9]+)(\.[0-9]+)?$") -gt 0 ]; then 23 | # must be a number 24 | __result="$__result\"$__key\":$__value," 25 | else 26 | # must be a string 27 | __result="$__result\"$__key\":\"$__value\"," 28 | fi 29 | fi 30 | done 31 | # remove comma on last value/name pair 32 | __result="${__result::-1}}" 33 | echo $__result 34 | } 35 | 36 | 37 | 38 | 39 | created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") 40 | state_id=6 41 | status_id=0 42 | state="OK" 43 | state="OK" 44 | mode="expired" 45 | xrig="xdripjs://$(hostname)" 46 | rssi=-84 47 | unfiltered=114.445 48 | filtered=116.445 49 | noise=0 50 | noiseString="Clean" 51 | voltagea=3.12 52 | voltageb=3.02 53 | txID="4XXXU4" 54 | 55 | 56 | function post_cgm_ns_pill() 57 | { 58 | 59 | jstr="$(build_json \ 60 | sessionStart "$lastSensorInsertDate" \ 61 | state "$state_id" \ 62 | txStatus "$status_id" \ 63 | stateString "$state" \ 64 | stateStringShort "$state" \ 65 | txId "$transmitter" \ 66 | txStatusString "$status" \ 67 | txStatusStringShort "$status" \ 68 | mode "$mode" \ 69 | timestamp "$epochdatems" \ 70 | rssi "$rssi" \ 71 | unfiltered "$unfiltered" \ 72 | filtered "$filtered" \ 73 | noise "$noise" \ 74 | noiseString "$noiseString" \ 75 | slope "$slope" \ 76 | intercept "$yIntercept" \ 77 | calType "$calibrationType" \ 78 | batteryTimestamp "$batteryTimestamp" \ 79 | voltagea "$voltagea" \ 80 | voltageb "$voltageb" \ 81 | temperature "$temperature" \ 82 | resistance "$resist" 83 | )" 84 | 85 | 86 | pill="[{\"device\":\"$xrig\",\"xdripjs\": $jstr, \"created_at\":\"$created_at\"}] " 87 | 88 | echo $pill && echo $pill > ./cgm-pill.json 89 | 90 | /usr/local/bin/g5-post-ns ./cgm-pill.json devicestatus && (echo; echo "Upload to NightScout of cgm status pill record entry worked";) || (echo; echo "Upload to NS of cgm status pill record did not work") 91 | } 92 | 93 | 94 | post_cgm_ns_pill 95 | -------------------------------------------------------------------------------- /test/noise/hi.csv: -------------------------------------------------------------------------------- 1 | 1558474038,2019-05-21 15:27,230,236,Flat,249,223,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-75 2 | 1558474338,2019-05-21 15:32,235,233,Flat,255,229,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-63 3 | 1558474638,2019-05-21 15:37,257,234,DoubleUp,280,258,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-76 4 | 1558474939,2019-05-21 15:42,271,243,DoubleUp,295,277,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-50 5 | 1558475238,2019-05-21 15:47,286,259,DoubleUp,312,293,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-63 6 | 1558475538,2019-05-21 15:52,303,277,DoubleUp,331,310,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-66 7 | 1558475839,2019-05-21 15:57,314,295,SingleUp,344,319,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-65 8 | 1558476138,2019-05-21 16:02,324,309,FortyFiveUp,355,327,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-63 9 | 1558476438,2019-05-21 16:07,331,319,FortyFiveUp,363,331,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-66 10 | 1558476738,2019-05-21 16:12,339,328,FortyFiveUp,372,338,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-63 11 | 1558477038,2019-05-21 16:17,341,335,Flat,374,337,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-58 12 | 1558477340,2019-05-21 16:22,346,340,Flat,380,340,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-60 13 | 1558477641,2019-05-21 16:27,349,344,Flat,383,343,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-60 14 | 1558477938,2019-05-21 16:32,354,348,Flat,389,347,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-68 15 | 1558478242,2019-05-21 16:37,354,351,Flat,389,346,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-70 16 | 1558478540,2019-05-21 16:42,350,353,Flat,385,340,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-51 17 | 1558478839,2019-05-21 16:47,342,352,FortyFiveDown,376,329,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-62 18 | 1558479138,2019-05-21 16:52,337,348,FortyFiveDown,370,322,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-74 19 | 1558479438,2019-05-21 16:57,332,341,FortyFiveDown,364,317,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-71 20 | 1558479740,2019-05-21 17:02,328,335,Flat,360,313,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-58 21 | 1558480038,2019-05-21 17:07,317,329,FortyFiveDown,347,302,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-60 22 | 1558480338,2019-05-21 17:12,309,322,FortyFiveDown,338,293,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-65 23 | 1558480638,2019-05-21 17:17,301,315,FortyFiveDown,329,285,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-61 24 | 1558480938,2019-05-21 17:22,295,306,FortyFiveDown,322,280,,0.886,8.852,0,5,0.98407,0.00,1,native-calibrates-lsr,0,1,-58 25 | -------------------------------------------------------------------------------- /test/noise/unit-test-noise.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | main() 4 | { 5 | inputFile="/tmp/cgm.csv" 6 | outputFile="/tmp/cgm.json" 7 | 8 | variation=0 9 | 10 | # comment/uncomment based on which test to run 11 | #variationTests=(0 5 10 15 20) 12 | variationTests=(0) 13 | 14 | for j in ${variationTests[@]}; do 15 | variation=$j 16 | echo "" 17 | echo "Testing scenarios where unfiltered / filtered variation = $variation" 18 | a=(310) && unit_test 19 | a=(310 315) && unit_test 20 | a=(310 315 320) && unit_test 21 | a=(310 315 320 325) && unit_test 22 | a=(310 335 305 395) && unit_test 23 | a=(310 335 480 325) && unit_test 24 | a=(110 111 110 110 110 110 110 160) && unit_test 25 | a=(97 94 96 93 93 94 93 96) && unit_test 26 | a=(125 123 124 122 122 122 123 124) && unit_test 27 | a=(116 114 112 108 103 96 101 108) && unit_test 28 | a=(82 80 75 68 59 58 65 75) && unit_test 29 | a=(200 201 200 201 200 201 200 201) && unit_test 30 | a=(100 102 104 102 104 106 104 102) && unit_test 31 | a=(75 75 75 74 75 75 75 75) && unit_test 32 | a=(76 66 59 55 58 80 92 100) && unit_test 33 | a=(110 111 110 110 110 110 110 110) && unit_test 34 | a=(176 166 159 155 158 180 192 200) && unit_test 35 | a=(122 135 140 155 160 155 150 145) && unit_test 36 | a=(100 105 110 120 135 137 135 125) && unit_test 37 | a=(100 110 130 160 190 191 184 180) && unit_test 38 | a=(143 140 137 135 131 129 125 119) && unit_test 39 | a=(140 120 140 160 180 200 220 240) && unit_test 40 | a=(100 115 114 130 129 140 139 150) && unit_test 41 | a=(100 102 104 102 104 106 104 152) && unit_test 42 | a=(100 105 104 110 109 115 114 120) && unit_test 43 | a=(100 152 104 106 108 108 108 108) && unit_test 44 | a=(120 105 110 120 135 134 135 125) && unit_test 45 | a=(150 110 140 170 200 230 260 290) && unit_test 46 | a=(150 110 140 170 200 130 160 290) && unit_test 47 | a=(110 122 144 166 188 212 230 264) && unit_test 48 | a=(176 146 159 135 158 150 282 200) && unit_test 49 | a=(260 230 200 170 140 110 80 50) && unit_test 50 | a=(100 120 140 160 180 200 220 240) && unit_test 51 | a=(110 111 110 110 110 310 315 318) && unit_test 52 | a=(110 111 110 110 110 110 315 318) && unit_test 53 | a=(110 111 110 110 110 110 110 180) && unit_test 54 | done 55 | 56 | } 57 | 58 | function unit_test() 59 | { 60 | truncate -s 0 $inputFile 61 | 62 | cgmTime=1000000000 63 | for t in ${a[@]}; do 64 | filtered=$(($t+$variation)) 65 | #echo "t=$t, variation=$variation" 66 | echo "$cgmTime,$t,$filtered,$t" >> $inputFile 67 | cgmTime=$(bc <<< "$cgmTime + 300") 68 | done 69 | 70 | numRecords=${#a[@]} 71 | yarr=( $(tail -$numRecords $inputFile | cut -d ',' -f2 ) ) 72 | # n=${#yarr[@]} 73 | #cat $inputFile 74 | echo -n "${yarr[@]} - " 75 | cgm-calc-noise $inputFile $outputFile 76 | } 77 | 78 | main "$@" 79 | 80 | -------------------------------------------------------------------------------- /test/noise/startup.csv: -------------------------------------------------------------------------------- 1 | 1558344441,2019-05-20 03:27,94,87,Flat,87,89,,1.174,-8.253,0,1,0.99911,0.00,1,native-calibrates-lsr,0,1,-86 2 | 1558344741,2019-05-20 03:32,99,90,FortyFiveUp,91,95,,1.174,-8.253,0,1,0.99911,0.00,1,native-calibrates-lsr,0,1,-87 3 | 1558345042,2019-05-20 03:37,103,95,Flat,94,99,,1.174,-8.253,0,1,0.99911,0.00,1,native-calibrates-lsr,0,1,-76 4 | 1558345642,2019-05-20 03:47,111,106,Flat,101,105,,1.174,-8.253,0,1,0.99911,0.00,1,native-calibrates-lsr,0,1,-77 5 | 1558345941,2019-05-20 03:52,114,110,Flat,104,106,,1.174,-8.253,0,1,0.99911,0.00,1,native-calibrates-lsr,0,1,-87 6 | 1558346241,2019-05-20 03:57,115,113,Flat,104,107,,1.174,-8.253,0,1,0.99911,0.00,1,native-calibrates-lsr,0,1,-86 7 | 1558346541,2019-05-20 04:02,115,114,Flat,104,105,,1.174,-8.253,0,1,0.99911,0.00,1,native-calibrates-lsr,0,1,-77 8 | 1558346841,2019-05-20 04:07,113,115,Flat,103,103,,1.174,-8.253,0,1,0.99911,0.00,1,native-calibrates-lsr,0,1,-77 9 | 1558347141,2019-05-20 04:12,111,114,Flat,101,100,,1.174,-8.253,0,1,0.99911,0.00,1,native-calibrates-lsr,0,1,-81 10 | 1558347441,2019-05-20 04:17,109,112,Flat,99,98,,1.174,-8.253,0,1,0.99911,0.00,1,native-calibrates-lsr,0,1,-81 11 | 1558347741,2019-05-20 04:22,107,110,Flat,98,96,,1.174,-8.253,0,1,0.99911,0.00,1,native-calibrates-lsr,0,1,-90 12 | 1558348041,2019-05-20 04:27,106,108,Flat,97,95,,1.174,-8.253,0,1,0.99911,0.00,1,native-calibrates-lsr,0,1,-82 13 | 1558358842,2019-05-20 07:27,31,3,NONE,39,0,,1.174,-8.253,0,1,0.99911,,4,expired,,1,-68 14 | 1558359142,2019-05-20 07:32,341,46,Flat,297,0,,1.174,-8.253,0,1,0.99911,0.00,1,expired,0,1,-66 15 | 1558359441,2019-05-20 07:37,201,130,DoubleUp,178,0,,1.174,-8.253,0,1,0.99911,,4,expired,,1,-66 16 | 1558359741,2019-05-20 07:42,146,209,DoubleDown,131,0,,1.174,-8.253,0,1,0.99911,,4,expired,,1,-69 17 | 1558360041,2019-05-20 07:47,121,232,DoubleDown,110,0,,1.174,-8.253,0,1,0.99911,0.00,1,expired,0,1,-56 18 | 1558360341,2019-05-20 07:52,116,184,SingleDown,105,0,,1.174,-8.253,0,1,0.99911,0.00,1,expired,0,1,-71 19 | 1558360641,2019-05-20 07:57,116,123,Flat,105,0,,1.174,-8.253,0,1,0.99911,0.00,1,expired,0,1,-48 20 | 1558360941,2019-05-20 08:02,130,96,FortyFiveUp,117,0,,1.174,-8.253,0,1,0.99911,0.00,1,expired,0,1,-44 21 | 1558361241,2019-05-20 08:07,148,106,SingleUp,133,0,,1.174,-8.253,0,1,0.99911,0.00,1,expired,0,1,-65 22 | 1558361540,2019-05-20 08:12,161,133,SingleUp,144,0,,1.174,-8.253,0,1,0.99911,0.00,1,expired,0,1,-64 23 | 1558361841,2019-05-20 08:17,171,157,FortyFiveUp,152,0,,1.174,-8.253,0,1,0.99911,0.00,1,expired,0,1,-46 24 | 1558362141,2019-05-20 08:22,180,171,FortyFiveUp,160,0,,1.174,-8.253,0,1,0.99911,0.00,1,expired,0,1,-50 25 | 1558362441,2019-05-20 08:27,187,178,FortyFiveUp,166,0,,1.174,-8.253,0,1,0.99911,0.00,1,expired,0,1,-49 26 | 1558362741,2019-05-20 08:32,188,182,Flat,167,0,,1.174,-8.253,0,1,0.99911,0.00,1,expired,0,1,-49 27 | 1558363042,2019-05-20 08:37,186,186,Flat,165,0,,1.174,-8.253,0,1,0.99911,0.00,1,expired,0,1,-63 28 | -------------------------------------------------------------------------------- /test/noise/lo.csv: -------------------------------------------------------------------------------- 1 | 1561630568,2019-06-27 04:16,101,117,FortyFiveDown,121,122,,1.093,-31.422,0,7,0.98839,0.00,1,native-calibrates-lsr,0,0.83,-83 2 | 1561630873,2019-06-27 04:21,91,106,SingleDown,112,112,,1.093,-31.422,0,7,0.98839,0.00,1,native-calibrates-lsr,0,0.83,-79 3 | 1561631168,2019-06-27 04:26,86,97,FortyFiveDown,107,108,,1.093,-31.422,0,7,0.98839,0.00,1,native-calibrates-lsr,0,0.83,-87 4 | 1561631474,2019-06-27 04:31,77,88,FortyFiveDown,99,99,,1.093,-31.422,0,7,0.98839,0.00,1,native-calibrates-lsr,0,0.83,-94 5 | 1561631775,2019-06-27 04:36,65,80,SingleDown,88,86,,1.093,-31.422,0,7,0.98839,0.00,1,native-calibrates-lsr,0,0.83,-97 6 | 1561632668,2019-06-27 04:51,43,53,DoubleDown,68,64,,1.093,-31.422,0,7,0.98839,0.00,1,native-calibrates-lsr,0,0.83,-76 7 | 1561632972,2019-06-27 04:56,40,46,Flat,65,63,,1.093,-31.422,0,7,0.98839,0.00,1,native-calibrates-lsr,0,0.83,-76 8 | 1561633268,2019-06-27 05:01,37,41,Flat,62,61,,1.093,-31.422,0,7,0.98839,0.00,1,native-calibrates-lsr,0,0.83,-74 9 | 1561633569,2019-06-27 05:06,32,38,Flat,58,56,,1.093,-31.422,0,7,0.98839,0.00,1,native-calibrates-lsr,0,0.83,-77 10 | 1561636572,2019-06-27 05:56,30,27,Flat,56,58,,1.093,-31.422,0,7,0.98839,0.00,1,native-calibrates-lsr,0,0.83,-81 11 | 1561636865,2019-06-27 06:01,30,28,Flat,55,58,58,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.83,-90 12 | 1561637164,2019-06-27 06:06,31,30,Flat,56,58,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.83,-84 13 | 1561637473,2019-06-27 06:11,32,31,Flat,57,59,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.83,-79 14 | 1561637768,2019-06-27 06:16,34,32,Flat,59,61,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.83,-79 15 | 1561638074,2019-06-27 06:21,36,33,Flat,61,64,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.83,-80 16 | 1561638369,2019-06-27 06:26,38,35,Flat,62,66,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.83,-80 17 | 1561638671,2019-06-27 06:31,41,37,Flat,65,69,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.83,-79 18 | 1561638968,2019-06-27 06:36,43,39,Flat,67,71,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.83,-71 19 | 1561639273,2019-06-27 06:41,39,41,Flat,63,65,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.83,-75 20 | 1561639573,2019-06-27 06:46,41,42,Flat,65,67,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.83,-76 21 | 1561639872,2019-06-27 06:51,44,42,Flat,68,70,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.83,-76 22 | 1561640168,2019-06-27 06:56,45,42,Flat,69,71,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.83,-72 23 | 1561640472,2019-06-27 07:01,46,43,Flat,70,73,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.8,-73 24 | 1561640767,2019-06-27 07:06,47,45,Flat,71,74,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.8,-73 25 | 1561641068,2019-06-27 07:11,49,47,Flat,73,75,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.8,-74 26 | 1561641367,2019-06-27 07:16,50,48,Flat,74,75,,1.064,-28.981,0,7,0.98093,0.00,1,native-calibrates-lsr,0,0.8,-76 27 | -------------------------------------------------------------------------------- /bin/logger-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script sets up the openaps rig by installing components required to run 4 | # Logger which retrieves information from a Dexcom Transmitter via BLE, processes 5 | # the information, and forwards it to Nightscout and openaps 6 | # Released under MIT license. See the accompanying LICENSE.txt file for 7 | # full terms and conditions 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 11 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 12 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 13 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 14 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 15 | # THE SOFTWARE. 16 | 17 | function link_install() 18 | { 19 | src=$1 20 | exe=$2 21 | bin="/usr/local/bin" 22 | rm -f "${bin}/${exe}" 23 | ln -s ${src} ${bin}/${exe} 24 | echo "ln -s ${src} ${bin}/${exe}" 25 | } 26 | 27 | function build_go_exe() 28 | { 29 | EXE=$1 30 | echo "building ${EXE}" 31 | cd ${HOME}/src/Logger/cmd/${EXE} 32 | /usr/local/go/bin/go build 33 | if [ -e ${EXE} ]; then 34 | echo "${EXE} build successful" 35 | else 36 | echo "${EXE} build not successful" 37 | fi 38 | } 39 | 40 | 41 | mkdir -p ${HOME}/myopenaps/monitor/xdripjs 42 | 43 | root_dir=${HOME}/src/Logger 44 | link_install ${root_dir}/bin/calibrate.sh calibrate 45 | link_install ${root_dir}/bin/calibrate.sh cgm-calibrate 46 | link_install ${root_dir}/bin/calibrate.sh g5-calibrate 47 | link_install ${root_dir}/bin/g5-noise.sh g5-noise 48 | link_install ${root_dir}/bin/g5-noise.sh cgm-noise 49 | link_install ${root_dir}/bin/g5-stop.sh g5-stop 50 | link_install ${root_dir}/bin/g5-stop.sh cgm-stop 51 | link_install ${root_dir}/bin/g5-start.sh g5-start 52 | link_install ${root_dir}/bin/g5-start.sh cgm-start 53 | link_install ${root_dir}/bin/g5-battery.sh g5-battery 54 | link_install ${root_dir}/bin/g5-battery.sh cgm-battery 55 | link_install ${root_dir}/bin/g5-insert.sh g5-insert 56 | link_install ${root_dir}/bin/g5-insert.sh cgm-insert 57 | link_install ${root_dir}/bin/g5-reset.sh g5-reset 58 | link_install ${root_dir}/bin/g5-reset.sh cgm-reset 59 | link_install ${root_dir}/bin/g5-version.sh g5-version 60 | link_install ${root_dir}/bin/g5-version.sh cgm-version 61 | link_install ${root_dir}/bin/g5-restart.sh cgm-restart 62 | link_install ${root_dir}/bin/g5-restart.sh g5-restart 63 | link_install ${root_dir}/bin/g5-calc-calibration.sh g5-calc-calibration 64 | link_install ${root_dir}/bin/g5-calc-calibration.sh cgm-calc-calibration 65 | link_install ${root_dir}/bin/g5-calc-noise.sh g5-calc-noise 66 | link_install ${root_dir}/bin/g5-calc-noise.sh cgm-calc-noise 67 | link_install ${root_dir}/bin/g5-post-ns.sh g5-post-ns 68 | link_install ${root_dir}/bin/g5-post-ns.sh cgm-post-ns 69 | link_install ${root_dir}/bin/g5-post-xdrip.sh g5-post-xdrip 70 | link_install ${root_dir}/bin/g5-post-xdrip.sh cgm-post-xdrip 71 | link_install ${root_dir}/bin/g5-transmitter.sh g5-transmitter 72 | link_install ${root_dir}/bin/g5-transmitter.sh cgm-transmitter 73 | link_install ${root_dir}/bin/g5-debug.sh cgm-debug 74 | link_install ${root_dir}/bin/g5-debug.sh logger-debug 75 | 76 | link_install ${root_dir}/xdrip-get-entries.sh Logger 77 | 78 | -------------------------------------------------------------------------------- /test/noise/noise-30.csv: -------------------------------------------------------------------------------- 1 | 1561584069,2019-06-26 15:21,180,163,SingleUp,186,197,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-75 2 | 1561584369,2019-06-26 15:26,186,174,FortyFiveUp,192,200,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-80 3 | 1561584670,2019-06-26 15:31,207,184,SingleUp,213,225,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-88 4 | 1561584969,2019-06-26 15:36,207,195,SingleUp,213,221,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-82 5 | 1561585269,2019-06-26 15:41,222,205,FortyFiveUp,227,237,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-80 6 | 1561585569,2019-06-26 15:46,231,216,SingleUp,236,245,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-79 7 | 1561585869,2019-06-26 15:51,237,225,FortyFiveUp,242,250,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-72 8 | 1561586168,2019-06-26 15:56,245,234,FortyFiveUp,250,258,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-63 9 | 1561586469,2019-06-26 16:01,232,239,FortyFiveDown,237,238,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-73 10 | 1561586769,2019-06-26 16:06,258,243,Flat,263,261,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-77 11 | 1561587069,2019-06-26 16:11,245,245,FortyFiveUp,250,256,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-68 12 | 1561587369,2019-06-26 16:16,224,245,FortyFiveDown,229,247,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-63 13 | 1561587670,2019-06-26 16:21,242,241,Flat,247,246,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-71 14 | 1561587969,2019-06-26 16:26,171,229,DoubleDown,177,0,,1.007,-7.545,0,1,0.99883,,4,expired,,0.83,-68 15 | 1561588269,2019-06-26 16:31,196,211,DoubleDown,202,0,,1.007,-7.545,0,1,0.99883,0.00,1,expired,0,0.83,-50 16 | 1561588569,2019-06-26 16:36,187,194,FortyFiveUp,193,0,,1.007,-7.545,0,1,0.99883,0.00,1,expired,0,0.83,-72 17 | 1561588868,2019-06-26 16:41,259,191,DoubleUp,264,0,,1.007,-7.545,0,1,0.99883,,4,expired,,0.83,-73 18 | 1561589169,2019-06-26 16:46,229,207,DoubleUp,234,0,,1.007,-7.545,0,1,0.99883,0.00,1,expired,0,0.83,-82 19 | 1561589469,2019-06-26 16:51,224,229,DoubleDown,229,0,,1.007,-7.545,0,1,0.99883,0.00,1,expired,0,0.83,-65 20 | 1561589768,2019-06-26 16:56,226,240,Flat,231,0,,1.007,-7.545,0,1,0.99883,0.00,1,expired,0,0.83,-73 21 | 1561590069,2019-06-26 17:01,230,236,Flat,235,0,,1.007,-7.545,0,1,0.99883,0.00,1,expired,0,0.83,-71 22 | 1561590369,2019-06-26 17:06,230,228,Flat,235,0,,1.007,-7.545,0,1,0.99883,0.00,1,expired,0,0.83,-79 23 | 1561590669,2019-06-26 17:11,236,226,Flat,241,0,,1.007,-7.545,0,1,0.99883,0.00,1,expired,0,0.83,-75 24 | 1561590970,2019-06-26 17:16,239,230,Flat,244,0,,1.007,-7.545,0,1,0.99883,0.00,1,expired,0,0.83,-75 25 | 1561591269,2019-06-26 17:21,239,236,Flat,244,241,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-75 26 | 1561591569,2019-06-26 17:26,245,240,Flat,250,246,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-89 27 | 1561591869,2019-06-26 17:31,239,242,Flat,244,246,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-70 28 | 1561592169,2019-06-26 17:36,249,243,Flat,254,249,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-75 29 | 1561592469,2019-06-26 17:41,252,245,Flat,257,254,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-88 30 | 1561592769,2019-06-26 17:46,261,249,FortyFiveUp,266,260,,1.007,-7.545,0,1,0.99883,0.00,1,native-calibrates-lsr,0,0.83,-79 31 | -------------------------------------------------------------------------------- /cmd/g5-calc-noise-go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/csv" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "log" 11 | "math" 12 | "os" 13 | "strconv" 14 | ) 15 | 16 | func usage() { 17 | fmt.Fprintf(os.Stderr, "usage: %s [options] inputcsvfile outputjsonfile\n", os.Args[0]) 18 | flag.PrintDefaults() 19 | os.Exit(1) 20 | } 21 | 22 | func Round(x float64, digits int) float64 { 23 | s := strconv.FormatFloat(x, 'f', digits, 64) 24 | var yo float64 25 | if _, err := fmt.Sscan(s, &yo); err != nil { 26 | log.Print("Calculate Noise Rounding - ", err) 27 | } 28 | return yo 29 | 30 | } 31 | 32 | type NoiseS struct { 33 | Noise float64 `json:"noise"` 34 | } 35 | 36 | func ReportNoiseAndExit(noise float64, outputFile string) { 37 | var output []NoiseS 38 | output = append(output, NoiseS{Noise: noise}) 39 | b, err := json.Marshal(output) 40 | if err == nil { 41 | fmt.Println(string(b)) 42 | 43 | _ = ioutil.WriteFile(outputFile, b, 0644) 44 | } else { 45 | log.Fatal(err) 46 | } 47 | 48 | os.Exit(1) 49 | } 50 | 51 | func main() { 52 | var xdate []float64 53 | var yarr []float64 54 | var xarr []float64 55 | var noise float64 = 0 56 | 57 | flag.Parse() 58 | 59 | flag.Usage = usage 60 | if flag.NArg() < 2 { 61 | usage() 62 | } 63 | 64 | inputFile, err := os.Open(flag.Arg(0)) 65 | if err != nil { 66 | log.Fatal("Cannot open input csv file - ", err) 67 | ReportNoiseAndExit(0, flag.Arg(1)) 68 | } 69 | defer inputFile.Close() 70 | 71 | reader := csv.NewReader(inputFile) 72 | 73 | lineCount := 0 74 | 75 | for { 76 | record, err := reader.Read() 77 | if err == io.EOF { 78 | break 79 | } else if err != nil { 80 | log.Fatal(err) 81 | usage() 82 | } 83 | 84 | var epochdate float64 85 | var unfiltered float64 86 | if _, err := fmt.Sscan(record[0], &epochdate); err != nil { 87 | fmt.Printf("%T, %v\n", epochdate, epochdate) 88 | log.Fatal("Issue with Input csv epoch date", err) 89 | } 90 | if _, err := fmt.Sscan(record[1], &unfiltered); err != nil { 91 | fmt.Printf("%T, %v\n", unfiltered, unfiltered) 92 | log.Fatal("Issue with Input unfiltered BG", err) 93 | } 94 | xdate = append(xdate, epochdate) 95 | yarr = append(yarr, unfiltered) 96 | lineCount += 1 97 | } 98 | 99 | var firstDate float64 = 0 100 | 101 | firstDate = xdate[0] 102 | 103 | for i := 0; i < lineCount; i++ { 104 | // use 30 multiplier to norm x axis 105 | xarr = append(xarr, ((xdate[i] - firstDate) * 30)) 106 | // fmt.Fprintf(os.Stderr, "xdate[%d]=%f, yarr[%d]=%f, xarr[%d]=%f\n", i, xdate[i], i, yarr[i], i, xarr[i]) 107 | 108 | } 109 | 110 | // sod = sum of distances 111 | var sod float64 = 0 112 | var overallDistance float64 = 0 113 | var lastDelta float64 = 0 114 | var n int = lineCount 115 | 116 | var y2y1Delta float64 = 0 117 | var x2x1Delta float64 = 0 118 | for i := 1; i < n; i++ { 119 | // time-based multiplier 120 | // y2y1Delta adds a multiplier that gives 121 | // higher priority to the latest BG's 122 | y2y1Delta = (yarr[i] - yarr[i-1]) * (1.0 + float64(i)/(float64(n)*4.0)) 123 | x2x1Delta = xarr[i] - xarr[i-1] 124 | if lastDelta > 0 && y2y1Delta < 0 { 125 | // for this single point, bg switched from positive delta to negative, 126 | //increase noise impact 127 | // this will not effect noise to much for a normal peak, 128 | //but will increase the overall noise value 129 | // in the case that the trend goes up/down multiple times 130 | // such as the bounciness of a dying sensor's signal 131 | y2y1Delta = y2y1Delta * 1.1 132 | 133 | } else if lastDelta < 0 && y2y1Delta > 0 { 134 | 135 | // switched from negative delta to positive, increase noise impact 136 | // in this case count the noise a bit more because it could indicate 137 | // a big "false" swing upwards which could 138 | // be troublesome if it is a false swing upwards and a loop 139 | // algorithm takes it into account as "clean" 140 | y2y1Delta = y2y1Delta * 1.2 141 | } 142 | lastDelta = y2y1Delta 143 | // fmt.Fprintf(os.Stderr, "yDelta=%f, xdelta=%f\n", y2y1Delta, x2x1Delta) 144 | sod += math.Sqrt(x2x1Delta*x2x1Delta + y2y1Delta*y2y1Delta) 145 | 146 | } 147 | y2y1Delta = yarr[n-1] - yarr[0] 148 | x2x1Delta = xarr[n-1] - xarr[0] 149 | overallDistance = math.Sqrt(x2x1Delta*x2x1Delta + y2y1Delta*y2y1Delta) 150 | if sod == 0 { 151 | noise = 0 152 | } else { 153 | noise = 1 - (overallDistance / sod) 154 | } 155 | 156 | noise = Round(noise, 5) 157 | sod = Round(sod, 5) 158 | overallDistance = Round(overallDistance, 5) 159 | log.Print("sod=", sod, ", overallDistance=", overallDistance, ", noise=", noise) 160 | ReportNoiseAndExit(noise, flag.Arg(1)) 161 | 162 | } 163 | -------------------------------------------------------------------------------- /bin/g5-calc-noise.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #"${epochdate},${unfiltered},${filtered},${calibratedBG}" >> ./noise-input.csv 4 | # calculate the noise from csv input (format shown above) and put the noise in ./noise.json 5 | # noise is a floating point number from 0 to 1 where 0 is the cleanest and 1 is the noisiest 6 | # also added multiplier to get more weight to the latest BG values 7 | # also added weight for points where the delta shifts from pos to neg or neg to pos (peaks/valleys) 8 | # the more peaks and valleys, the more noise is amplified 9 | # add 0.06 (times oldness degrading factor) for each peak/valley 10 | # allow 7 point rises without adding noise 11 | 12 | inputFile=${1:-"${HOME}/myopenaps/monitor/xdripjs/noise-input41.csv"} 13 | outputFile=${2:-"${HOME}/myopenaps/monitor/xdripjs/noise.json"} 14 | MAXRECORDS=12 15 | MINRECORDS=3 16 | PEAK_VALLEY_FACTOR=0.06 # Higher means more weight on peaks / valleys 17 | RISE_WITHOUT_ADDED_NOISE=12 # Lower means more weight on deltas 18 | CLEAN_MAX_AVG_DELTA=6 19 | LIGHT_MAX_AVG_DELTA=9 20 | NO_NOISE=0.00 21 | CLEAN_MAX_NOISE=0.45 22 | LIGHT_MAX_NOISE=0.60 23 | MEDIUM_MAX_NOISE=0.75 24 | HEAVY_MAX_NOISE=1.00 25 | # This variable will be "41minutes" unless variation of filtered / unfiltered gives a higher noise, then it will be "lastVariation" 26 | calculatedBy="41minutes" 27 | 28 | function ReportNoiseAndExit() 29 | { 30 | if [ $(bc -l <<< "$noise <= $CLEAN_MAX_NOISE ") -eq 1 ]; then 31 | noiseSend=1 32 | noiseString="Clean" 33 | elif [ $(bc -l <<< "$noise <= $LIGHT_MAX_NOISE") -eq 1 ]; then 34 | noiseSend=2 35 | noiseString="Light" 36 | elif [ $(bc -l <<< "$noise <= $MEDIUM_MAX_NOISE") -eq 1 ]; then 37 | noiseSend=3 38 | noiseString="Medium" 39 | elif [ $(bc -l <<< "$noise > $MEDIUM_MAX_NOISE") -eq 1 ]; then 40 | noiseSend=4 41 | noiseString="Heavy" 42 | fi 43 | 44 | echo "[{\"noise\":$noise, \"noiseSend\":$noiseSend, \"noiseString\":\"$noiseString\",\"calculatedBy\":\"$calculatedBy\"}]" > $outputFile 45 | cat $outputFile 46 | exit 47 | } 48 | 49 | if [ -e $inputFile ]; then 50 | unfilteredArray=( $(tail -$MAXRECORDS $inputFile | cut -d ',' -f2 ) ) 51 | filteredArray=( $(tail -$MAXRECORDS $inputFile | cut -d ',' -f3 ) ) 52 | n=${#unfilteredArray[@]} 53 | else 54 | noise=$HEAVY_MAX_NOISE # Heavy if no input file 55 | calculatedBy="noInput" 56 | ReportNoiseAndExit 57 | fi 58 | 59 | 60 | if [ $(bc <<< "$n < $MINRECORDS") -eq 1 ]; then 61 | # set noise = 0 - unknown 62 | noise=$MEDIUM_MAX_NOISE # Light if not enough records, just starting out 63 | calculatedBy="tooFewRecords" 64 | ReportNoiseAndExit 65 | fi 66 | 67 | #echo ${unfilteredArray[@]} 68 | #echo ${filteredArray[@]} 69 | 70 | sod=0 71 | lastDelta=0 72 | noise=$NO_NOISE 73 | for (( i=1; i<$n; i++ )) 74 | do 75 | delta=$(bc <<< "${unfilteredArray[$i]} - ${unfilteredArray[$i-1]}") 76 | if [ $(bc <<< "$lastDelta > 0") -eq 1 -a $(bc <<< "$delta < 0") -eq 1 ]; then 77 | # this is a peak and change of direction 78 | # the older the peak, the less add to noise 79 | noise=$(bc -l <<< "$noise + $PEAK_VALLEY_FACTOR * (($n - $i * 0.5)/$n)") 80 | elif [ $(bc <<< "$lastDelta < 0") -eq 1 -a $(bc <<< "$delta > 0") -eq 1 ]; then 81 | # this is a valley and change of direction 82 | # the older the valley, the less add to noise 83 | noise=$(bc -l <<< "$noise + $PEAK_VALLEY_FACTOR * (($n - $i * 0.5)/$n)") 84 | fi 85 | 86 | absDelta=$delta 87 | if [ $(bc <<< "$delta < 0") -eq 1 ]; then 88 | absDelta=$(bc <<< "0 - $delta") 89 | fi 90 | # calculate sum of distances (all deltas) 91 | sod=$(bc <<< "$sod + $absDelta") 92 | 93 | # Any single jump amount over a certain limit increases noise linearly 94 | remainder=$(bc <<< "$absDelta - $RISE_WITHOUT_ADDED_NOISE") 95 | if [ $(bc <<< "$remainder > 0") -eq 1 ]; then 96 | # noise higher impact for latest bg, thus the smaller denominator for the remainder fraction 97 | noise=$(bc -l <<< "$noise + $remainder/(300 - $i*30)") 98 | else 99 | remainder=0 100 | fi 101 | 102 | #echo "lastdelta=$lastDelta, delta=$delta, remainder=$remainder" 103 | #echo "sod=$sod, noise=$noise, absDelta=$absDelta" 104 | 105 | lastDelta=$delta 106 | done 107 | 108 | # to ensure mostly straight lines with small bounces don't give heavy noise 109 | if [ $(bc -l <<< "$noise > $CLEAN_MAX_NOISE") -eq 1 ]; then 110 | if [ $(bc -l <<< "($sod / $n) < $CLEAN_MAX_AVG_DELTA") -eq 1 ]; then 111 | noise=$CLEAN_MAX_NOISE # very small up/downs shouldn't cause noise 112 | elif [ $(bc -l <<< "($sod / $n) < $LIGHT_MAX_AVG_DELTA") -eq 1 ]; then 113 | noise=$LIGHT_MAX_NOISE # small up/downs shouldn't cause Medium Heavy noise 114 | fi 115 | fi 116 | 117 | # get latest filtered / unfiltered for variation check 118 | filtered=${filteredArray[$n-1]} 119 | unfiltered=${unfilteredArray[$n-1]} 120 | #echo "filtered=$filtered, unfiltered=$unfiltered" 121 | # calculate alternate form of noise from last variation 122 | variationNoise=$(bc -l <<< "((($filtered - $unfiltered) * 1.3) / $filtered)") 123 | 124 | # make sure the variationNoise is positive 125 | if [ $(bc -l <<< "$variationNoise < 0") -eq 1 ]; then 126 | variationNoise=$(bc -l <<< "0 - $variationNoise") 127 | fi 128 | 129 | 130 | # If the variationNoise is higher than the 41minute calculated noise, then use variationNoise it instead 131 | if [ $(bc -l <<< "$variationNoise > $noise") -eq 1 ]; then 132 | noise=$variationNoise 133 | calculatedBy="lastVariation" 134 | fi 135 | 136 | # Cap noise at 1 as the highest value 137 | if [ $(bc -l <<< "$noise > 1") -eq 1 ]; then 138 | noise=$HEAVY_MAX_NOISE 139 | fi 140 | 141 | noise=$(printf "%.*f\n" 5 $noise) 142 | ReportNoiseAndExit 143 | 144 | -------------------------------------------------------------------------------- /cmd/g5-calc-calibration-go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/csv" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "math" 11 | "os" 12 | ) 13 | 14 | // test.csv order is from oldest calibration at the top 15 | // to latest calibration at the bottom 16 | // 17 | // test.csv in the form of 18 | // unfiltered,meterbg,datetime 19 | //Rule 1 - Clear calibration records upon CGM Sensor Change/Insert 20 | //Rule 2 - Don't allow any BG calibrations or take in any new calibrations 21 | // within 15 minutes of last sensor insert 22 | //Rule 3 - Only use SinglePoint Calibration for 1st 12 hours since Sensor insert 23 | //Rule 4 - Do not store calibration records w/in 12 hours since Sensor insert. 24 | // Use for SinglePoint calibration, but then discard them 25 | //Rule 5 - Do not use LSR until we have 4 or more calibration points. 26 | // Use SinglePoint calibration only for < than 4 calibration points. 27 | // SinglePoint simply uses the latest calibration record and assumes 28 | // the yIntercept is 0. 29 | //Rule 6 - Drop back to SinglePoint calibration if slope is out of bounds 30 | // (>MAXSLOPE or minimum unfiltered value in calibration record set or 33 | // < - minimum unfiltered value in calibration record set) 34 | // 35 | // yarr = array of last unfiltered values associated w/ bg meter checks 36 | // xarr = array of last bg meter check bg values 37 | 38 | func usage() { 39 | fmt.Fprintf(os.Stderr, "usage: %s [options] inputcsvfile outputjsonfile\n", os.Args[0]) 40 | flag.PrintDefaults() 41 | os.Exit(1) 42 | } 43 | 44 | func MathMin(xs []float64) float64 { 45 | var min float64 = xs[0] 46 | for _, v := range xs { 47 | if v < min { 48 | min = v 49 | } 50 | } 51 | return min 52 | } 53 | 54 | func MathAvg(xs []float64) float64 { 55 | total := MathSum(xs) 56 | return total / float64(len(xs)) 57 | } 58 | 59 | func MathSum(xs []float64) float64 { 60 | var sum float64 = 0 61 | for _, v := range xs { 62 | sum += v 63 | } 64 | return sum 65 | } 66 | 67 | func MathStdDev(xs []float64) float64 { 68 | mean := MathAvg(xs) 69 | var sqdif float64 = 0 70 | var stddev float64 = 0 71 | 72 | n := 0 73 | for _, v := range xs { 74 | sqdif += ((v - mean) * (v - mean)) 75 | n++ 76 | } 77 | stddev = math.Sqrt(sqdif / (float64(n) - 1)) 78 | return stddev 79 | } 80 | 81 | var tarr []float64 82 | var tdate []float64 83 | var yarr []float64 84 | var xarr []float64 85 | var numx int = 0 86 | 87 | //var slope float64 = 1000 88 | var yIntercept float64 = 0 89 | 90 | //var slopeError float64 = 0 91 | var yError float64 = 0 92 | 93 | //var rSquared float64 = 0 94 | var calibrationType string = "SinglePoint" 95 | 96 | type CalibrationS struct { 97 | Slope float64 `json:"slope"` 98 | YIntercept float64 `json:"yIntercept"` 99 | Formula string `json:"formula"` 100 | YError float64 `json:"yError"` 101 | SlopeError float64 `json:"slopeError"` 102 | RSquared float64 `json:"rSquared"` 103 | NumCalibrations int `json:"numCalibrations"` 104 | CalibrationType string `json:"calibrationType"` 105 | } 106 | 107 | var c CalibrationS 108 | 109 | func ReportCalibrationAndExit(outputFile string) { 110 | // c.YIntercept = yIntercept 111 | c.Formula = "whatever" 112 | c.YError = yError 113 | c.NumCalibrations = numx 114 | c.CalibrationType = "SinglePoint" 115 | 116 | b, err := json.Marshal(c) 117 | if err == nil { 118 | fmt.Println(string(b)) 119 | } 120 | 121 | _ = ioutil.WriteFile(outputFile, b, 0644) 122 | os.Exit(1) 123 | } 124 | 125 | func LeastSquaresRegression() { 126 | sumX := MathSum(xarr) 127 | sumY := MathSum(yarr) 128 | meanX := MathAvg(xarr) 129 | meanY := MathAvg(yarr) 130 | stddevX := MathStdDev(xarr) 131 | stddevY := MathStdDev(yarr) 132 | var sumXY float64 = 0 133 | var sumXSq float64 = 0 134 | var sumYSq float64 = 0 135 | var r float64 = 0 136 | var n int = numx 137 | var denominator float64 = 1 138 | 139 | var firstDate float64 = tdate[0] 140 | 141 | for i, v := range tdate { 142 | tarr[i] = v - firstDate 143 | } 144 | 145 | for i, _ := range tarr { 146 | var multiplier float64 = 1 147 | if i > 0 { 148 | multiplier = 1 + tarr[i-1]/(tarr[n-1]*2) 149 | } 150 | // bounds check 151 | if multiplier < 1 || multiplier > 2 { 152 | multiplier = 1 153 | } 154 | sumXY += xarr[i] * yarr[i] * multiplier 155 | sumXSq += xarr[i] * xarr[i] * multiplier 156 | sumYSq += yarr[i] * yarr[i] * multiplier 157 | } 158 | denominator = math.Sqrt((float64(n)*sumXSq - sumX*sumX) * (float64(n)*sumYSq - sumY*sumY)) 159 | if denominator == 0 || stddevX == 0 { 160 | c.Slope = 1000 161 | c.YIntercept = 0 162 | } else { 163 | calibrationType = "LeastSquaresRegression" 164 | r = (float64(n)*sumXY - sumX*sumY) / denominator 165 | c.RSquared = r * r 166 | c.Slope = r * stddevY / stddevX 167 | c.YIntercept = meanY - c.Slope*meanX 168 | 169 | // calculate error 170 | var varSum float64 = 0 171 | for j, _ := range tarr { 172 | var tf float64 = yarr[j] - c.YIntercept - c.Slope*xarr[j] 173 | varSum += tf * tf 174 | } 175 | var delta float64 = float64(n)*sumXSq - sumX*sumX 176 | var vari float64 = 1.0 / (float64(n) - 2.0) * varSum 177 | 178 | yError = math.Sqrt(vari / delta * sumXSq) 179 | c.SlopeError = math.Sqrt(float64(n) / delta * vari) 180 | } 181 | 182 | } 183 | 184 | func main() { 185 | 186 | flag.Parse() 187 | 188 | flag.Usage = usage 189 | if flag.NArg() < 2 { 190 | usage() 191 | } 192 | 193 | inputFile, err := os.Open(flag.Arg(0)) 194 | if err != nil { 195 | fmt.Println("Cannot open input file", flag.Arg(0)) 196 | fmt.Println("Error:", err) 197 | ReportCalibrationAndExit(flag.Arg(1)) 198 | } 199 | defer inputFile.Close() 200 | 201 | ReportCalibrationAndExit(flag.Arg(1)) 202 | 203 | reader := csv.NewReader(inputFile) 204 | 205 | lineCount := 0 206 | 207 | for { 208 | record, err := reader.Read() 209 | if err == io.EOF { 210 | break 211 | } else if err != nil { 212 | fmt.Println("Error:", err) 213 | usage() 214 | } 215 | 216 | var epochdate float64 217 | var bg float64 218 | var unfiltered float64 219 | if _, err := fmt.Sscan(record[3], &epochdate); err != nil { 220 | fmt.Printf("%T, %v\n", epochdate, epochdate) 221 | } 222 | if _, err := fmt.Sscan(record[1], &bg); err != nil { 223 | fmt.Printf("%T, %v\n", bg, bg) 224 | } 225 | if _, err := fmt.Sscan(record[0], unfiltered); err != nil { 226 | fmt.Printf("%T, %v\n", unfiltered, unfiltered) 227 | } 228 | tdate = append(tdate, epochdate) 229 | yarr = append(yarr, bg) 230 | xarr = append(xarr, unfiltered) 231 | lineCount += 1 232 | } 233 | 234 | //var firstDate float64 = 0 235 | 236 | //firstDate = 0 237 | 238 | // sod = sum of distances 239 | var sod float64 = 0 240 | var lastDelta float64 = 0 241 | var n int = lineCount 242 | 243 | var y2y1Delta float64 = 0 244 | var x2x1Delta float64 = 0 245 | for i := 1; i < n; i++ { 246 | // time-based multiplier 247 | // y2y1Delta adds a multiplier that gives 248 | // higher priority to the latest BG's 249 | y2y1Delta = (yarr[i] - yarr[i-1]) * (1.0 - (float64(n)-float64(i))/(float64(n)*3.0)) 250 | x2x1Delta = xarr[i] - xarr[i-1] 251 | if lastDelta > 0 && y2y1Delta < 0 { 252 | // for this single point, bg switched from positive delta to negative, 253 | //increase noise impact 254 | // this will not effect noise to much for a normal peak, 255 | //but will increase the overall noise value 256 | // in the case that the trend goes up/down multiple times 257 | // such as the bounciness of a dying sensor's signal 258 | y2y1Delta = y2y1Delta * 1.1 259 | 260 | } else if lastDelta < 0 && y2y1Delta > 0 { 261 | 262 | // switched from negative delta to positive, increase noise impact 263 | // in this case count the noise a bit more because it could indicate 264 | // a big "false" swing upwards which could 265 | // be troublesome if it is a false swing upwards and a loop 266 | // algorithm takes it into account as "clean" 267 | y2y1Delta = y2y1Delta * 1.2 268 | } 269 | lastDelta = y2y1Delta 270 | // fmt.Fprintf(os.Stderr, "yDelta=%f, xdelta=%f\n", y2y1Delta, x2x1Delta) 271 | sod = sod + math.Sqrt(x2x1Delta*x2x1Delta+y2y1Delta*y2y1Delta) 272 | 273 | } 274 | y2y1Delta = yarr[n-1] - yarr[0] 275 | x2x1Delta = xarr[n-1] - xarr[0] 276 | if sod == 0 { 277 | //noise = 0 278 | } else { 279 | //noise = 1 - (overallDistance / sod) 280 | } 281 | 282 | // fmt.Fprintf(os.Stderr, "sod=%f, overallDistance=%f, noise=%f\n", sod, overallDistance, noise) 283 | ReportCalibrationAndExit(flag.Arg(1)) 284 | 285 | } 286 | 287 | func SinglePointCalibration() { 288 | c.Slope = 1000 289 | c.YIntercept = 0 290 | if numx > 0 { 291 | var x float64 = xarr[numx-1] 292 | var y float64 = yarr[numx-1] 293 | c.Slope = y / x 294 | calibrationType = "SinglePoint" 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /bin/g5-calc-calibration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # test.csv order is from oldest calibration at the top to latest calibration at the bottom 4 | # test.csv in the form of 5 | # unfiltered,meterbg,datetime 6 | #Rule 1 - Clear calibration records upon CGM Sensor Change/Insert 7 | #Rule 2 - Don't allow any BG calibrations or take in any new calibrations 8 | # within 15 minutes of last sensor insert 9 | #Rule 3 - Only use Single Point Calibration for 1st 12 hours since Sensor insert 10 | #Rule 4 - Do not store calibration records within 12 hours since Sensor insert. 11 | # Use for SinglePoint calibration, but then discard them 12 | #Rule 5 - Do not use LSR until we have 4 or more calibration points. 13 | # Use SinglePoint calibration only for less than 4 calibration points. 14 | # SinglePoint simply uses the latest calibration record and assumes 15 | # the yIntercept is 0. 16 | #Rule 6 - Drop back to SinglePoint calibration if slope is out of bounds 17 | # (>MAXSLOPE or minimum unfiltered value in calibration record set or 20 | # < - minimum unfiltered value in calibration record set) 21 | # 22 | # yarr = array of last unfiltered values associated w/ bg meter checks 23 | # xarr = array of last bg meter check bg values 24 | 25 | INPUT=${1:-"${HOME}/myopenaps/monitor/xdripjs/calibrations.csv"} 26 | OUTPUT=${2:-"${HOME}/myopenaps/monitor/xdripjs/calibration-linear.json"} 27 | MAXSLOPE=1.750 28 | MINSLOPE=0.550 29 | MINSLOPESINGLE=0.800 30 | maxRecords=12 31 | MINRECORDSFORLSR=3 32 | rSquared=0 33 | epochdate=$(date +'%s') 34 | 35 | echo "Begin calibration using input of $INPUT and output of $OUTPUT" 36 | 37 | tdate=( $(tail -$maxRecords $INPUT | cut -d ',' -f4 ) ) 38 | 39 | # set totalRecordsToUse so that only the last 3 days worth of calibrations will be considered 40 | totalRecordsToUse=${#tdate[@]} 41 | for (( m=$totalRecordsToUse-1; m>0; m-- )) 42 | do 43 | 44 | if [ $(bc <<< "${tdate[$m]} < $epochdate - (3600 * 24 * 3)") -eq 1 ]; then 45 | totalRecordsToUse=$(bc <<< "$totalRecordsToUse - 1") 46 | fi 47 | done 48 | echo "Total Records to use based on last 3 days calibration records = $totalRecordsToUse" 49 | 50 | 51 | 52 | yarr=( $(tail -$totalRecordsToUse $INPUT | cut -d ',' -f1 ) ) 53 | xarr=( $(tail -$totalRecordsToUse $INPUT | cut -d ',' -f2 ) ) 54 | tdate=( $(tail -$totalRecordsToUse $INPUT | cut -d ',' -f4 ) ) 55 | 56 | 57 | 58 | # so that upgades will work for old 1000x scale calibration.csv data 59 | for (( k=0; k<${#yarr[@]}; k++ )) 60 | do 61 | if [ $(bc -l <<< "${yarr[$k]} > 1000") -eq 1 ]; then 62 | yarr[$k]=$(bc -l <<< "scale=3; ${yarr[$k]} / 1000") 63 | fi 64 | done 65 | 66 | 67 | function MathMin() 68 | { 69 | local arr=("$@") 70 | local n=${#arr[@]} 71 | local min=${arr[0]} 72 | 73 | 74 | for (( i=1; i<$n; i++ )) 75 | do 76 | if [ $(bc -l <<< "${arr[$i]} < $min") -eq 1 ]; then 77 | min=${arr[$i]} 78 | fi 79 | done 80 | 81 | echo "$min" 82 | } 83 | 84 | function MathSum() 85 | { 86 | local total=0 87 | 88 | for i in "$@" 89 | do 90 | total=$(bc -l <<< "$total + $i") 91 | done 92 | 93 | echo "$total" 94 | } 95 | 96 | function MathAvg() 97 | { 98 | local arr=("$@") 99 | local total=$(MathSum "${arr[@]}") 100 | local avg=0 101 | 102 | avg=$(bc -l <<< "$total / ${#arr[@]}") 103 | 104 | echo "$avg" 105 | } 106 | 107 | function MathStdDev() 108 | { 109 | local arr=("$@") 110 | local mean=$(MathAvg "${arr[@]}") 111 | local sqdif=0 112 | local stddev=0 113 | 114 | for i in "${arr[@]}" 115 | do 116 | sqdif=$(bc -l <<< "$sqdif + ($i-$mean)^2") 117 | done 118 | 119 | stddev=$(bc -l <<< "sqrt($sqdif / (${#arr[@]} - 1))") 120 | echo $stddev 121 | } 122 | 123 | 124 | # Assumes global arrays x and y are set 125 | # sets global variables slope and yIntercept 126 | function LeastSquaresRegression() 127 | { 128 | local sumX=$(MathSum "${xarr[@]}") 129 | local sumY=$(MathSum "${yarr[@]}") 130 | local meanX=$(MathAvg "${xarr[@]}") 131 | local meanY=$(MathAvg "${yarr[@]}") 132 | local stddevX=$(MathStdDev "${xarr[@]}") 133 | local stddevY=$(MathStdDev "${yarr[@]}") 134 | local sumXY=0 135 | local sumXSq=0 136 | local sumYSq=0 137 | local r=0 138 | local n=${#xarr[@]} 139 | 140 | usingDates=0 141 | local firstDate=${tdate[0]} 142 | local re='^[0-9]+$' 143 | if [[ $firstDate =~ $re ]]; then 144 | for (( i=0; i<$n; i++ )) 145 | do 146 | tarr[$i]=$(bc <<< "${tdate[$i]} - $firstDate") 147 | done 148 | # avoid divide by zero if times are somehow the same in csv input file (shouldn't be) 149 | if [ $(bc <<< "${tarr[$n-1]} != 0") -eq 1 ]; then 150 | usingDates=1 151 | fi 152 | fi 153 | 154 | local multiplier=1 155 | 156 | 157 | for (( i=0; i<$n; i++ )) 158 | do 159 | if [ $(bc <<< "$i != 0") -eq 1 -a $(bc <<< "$usingDates == 1") -eq 1 ]; then 160 | multiplier=$(bc -l <<< "1 + ${tarr[$i-1]} / (${tarr[$n-1]} * 2)") 161 | # boundary check 162 | if [ $(bc -l <<< "$multiplier < 1") -eq 1 -o $(bc -l <<< "$multiplier > 2") -eq 1 ]; then 163 | multiplier=1 164 | fi 165 | fi 166 | echo "Calibration - record $i, time(${tdate[$i]}), weighted multiplier=$multiplier" 167 | sumXY=$(bc -l <<< "($sumXY + ${xarr[i]} * ${yarr[i]}) * $multiplier") 168 | sumXSq=$(bc -l <<< "($sumXSq + ${xarr[i]} * ${xarr[i]}) * $multiplier") 169 | sumYSq=$(bc -l <<< "($sumYSq + ${yarr[i]} * ${yarr[i]}) * $multiplier") 170 | done 171 | denominator=$(bc -l <<< "sqrt((($n * $sumXSq - ${sumX}^2) * ($n * $sumYSq - ${sumY}^2)))") 172 | if [ $(bc <<< "$denominator == 0") -eq 1 -o $(bc <<< "$stddevX == 0") -eq 1 ] ; then 173 | slope=1 174 | yIntercept=0 175 | else 176 | r=$(bc -l <<< "($n * $sumXY - $sumX * $sumY) / $denominator") 177 | rSquared=$(bc -l <<< "(${r})^2") 178 | rSquared=$(printf "%.*f\n" 5 $rSquared) 179 | 180 | 181 | slope=$(bc -l <<< "scale=5; $r * $stddevY / $stddevX ") 182 | yIntercept=$(bc -l <<< "scale=5; $meanY - $slope * $meanX ") 183 | 184 | 185 | # calculate error 186 | local varSum=0 187 | for (( j=0; j<$n; j++ )) 188 | do 189 | varSum=$(bc -l <<< "$varSum + (${yarr[$j]} - $yIntercept - $slope * ${xarr[$j]})^2") 190 | done 191 | 192 | local delta=$(bc -l <<< "$n * $sumXSq - ($sumX)^2") 193 | local vari=$(bc -l <<< "1.0 / ($n - 2.0) * $varSum") 194 | 195 | yError=$(bc -l <<< "sqrt($vari / $delta * $sumXSq)") 196 | slopeError=$(bc -l <<< "sqrt($n / $delta * $vari)") 197 | fi 198 | } 199 | 200 | 201 | 202 | function SinglePointCalibration 203 | { 204 | if [ "$numx" -gt "0" ]; then 205 | x=${xarr[-1]} 206 | y=${yarr[-1]} 207 | yIntercept=0 208 | slope=$(bc -l <<< "scale=5; $y / $x") 209 | calibrationType="SinglePoint" 210 | echo "x=$x, y=$y, slope=$slope, yIntercept=0" 211 | fi 212 | } 213 | 214 | #echo "${xarr[@]}" 215 | 216 | #get the number of calibrations 217 | numx=${#xarr[@]} 218 | slope=1 219 | yIntercept=0 220 | slopeError=0 221 | yError=0 222 | 223 | if [ $(bc -l <<< "$numx >= $MINRECORDSFORLSR") -eq 1 ]; then 224 | echo "Calibration records = $numx, attempting to use LeastSquaresRegression" 225 | LeastSquaresRegression 226 | calibrationType="LeastSquaresRegression" 227 | elif [ $(bc -l <<< "$numx > 0") -eq 1 ]; then 228 | echo "Calibration records = $numx, using single point linear" 229 | SinglePointCalibration 230 | else 231 | slope=1 232 | yIntercept=0 233 | fi 234 | 235 | # truncate and bounds check 236 | #yIntercept=$(bc <<< "$yIntercept / 1") # truncate 237 | #slope=$(bc <<< "$slope / 1") # truncate 238 | yError=$(bc <<< "$yError / 1") # truncate 239 | slopeError=$(bc <<< "$slopeError / 1") # truncate 240 | 241 | # Set max yIntercept to the minimum of the set of unfiltered values 242 | maxIntercept=$(MathMin "${yarr[@]}") 243 | 244 | echo "Calibration - Before bounds check, slope=$slope, yIntercept=$yIntercept" 245 | 246 | # Check for boundaries and fall back to Single Point Calibration if necessary 247 | if [ $(bc -l <<< "$slope > $MAXSLOPE") -eq 1 ]; then 248 | echo "slope of $slope > maxSlope of $MAXSLOPE, using single point linear" 249 | SinglePointCalibration 250 | elif [ $(bc -l <<< "$slope < $MINSLOPE") -eq 1 ]; then 251 | echo "slope of $slope < minSlope of $MINSLOPE, using single point linear" 252 | SinglePointCalibration 253 | fi 254 | 255 | if [ $(bc -l <<< "$yIntercept > $maxIntercept") -eq 1 ]; then 256 | echo "yIntercept of $yIntercept > maxIntercept of $maxIntercept, using single point linear" 257 | SinglePointCalibration 258 | elif [ $(bc -l <<< "$yIntercept < (0 - $maxIntercept)") -eq 1 ]; then 259 | echo "yIntercept of $yIntercept < negative maxIntercept of -$maxIntercept, using single point linear" 260 | SinglePointCalibration 261 | echo "x=$x, y=$y, slope=$slope, yIntercept=0" 262 | fi 263 | 264 | 265 | # check slope again if SinglePoint for safety 266 | # this is for the case that we fell back to SinglePoint 267 | # to make sure that we don't have use without bounds a potentiall bad 268 | # or mistaken calibration recent record 269 | if [ "$calibrationType" == "SinglePoint" ]; then 270 | if [ $(bc -l <<< "$slope > $MAXSLOPE") -eq 1 ]; then 271 | echo "single point slope of $slope > maxSlope of $MAXSLOPE, using $MAXSLOPE" 272 | slope=$MAXSLOPE 273 | elif [ $(bc -l <<< "$slope < $MINSLOPESINGLE") -eq 1 ]; then 274 | echo "single point slope of $slope < minSlope of $MINSLOPESINGLE, using $MINSLOPESINGLE" 275 | slope=$MINSLOPESINGLE 276 | fi 277 | fi 278 | 279 | #yIntercept=$(bc <<< "$yIntercept / 1") # truncate 280 | #slope=$(bc <<< "$slope / 1") # truncate 281 | 282 | echo "Calibration - After bounds check, slope=$slope, yIntercept=$yIntercept" 283 | echo "Calibration - slopeError=$slopeError, yError=$yError" 284 | 285 | # store the calibration in a json file for use by xdrip-get-entries.sh 286 | echo "[{\"slope\":$(printf "%10.3f" $slope), \"yIntercept\":$(printf "%10.3f" $yIntercept), \"formula\":\"calibratedbg=(unfiltered-yIntercept)/slope\", \"yError\":$yError, \"slopeError\":${slopeError}, \"rSquared\":${rSquared}, \"numCalibrations\":${numx}, \"calibrationType\":\"${calibrationType}\"}]" > $OUTPUT 287 | 288 | echo "Calibration - Created $OUTPUT" 289 | cat $OUTPUT 290 | 291 | #ConvertDateArray 292 | 293 | 294 | -------------------------------------------------------------------------------- /logger/index.js: -------------------------------------------------------------------------------- 1 | const Transmitter = require('xdrip-js'); 2 | const util = require('util') 3 | 4 | const id = process.argv[2]; 5 | var messages = '[]'; 6 | // examples mesages are: {date: Date.now(), type: "CalibrateSensor", glucose} or {date: Date.now(), type: "StopSensor"} or {date: Date.now(), type: "StartSensor"} 7 | var fs2 = require('fs'); 8 | fs2.readFile(process.argv[3], function (err, data) { 9 | // var json = []; 10 | if (data && data.length > 0) { 11 | console.log('messages passed to logger: ' + data); 12 | messages = JSON.parse(data); 13 | const util = require('util') 14 | } 15 | }); 16 | 17 | //const messages = JSON.parse(process.argv[3] || '[]'); 18 | // arg 4 is "true" if using alternate transmitter bluetooth channel 19 | const arg4 = process.argv[4]; 20 | var alternateBluetooth = false; 21 | 22 | if ( arg4 == "true" ) { 23 | alternateBluetooth = true; 24 | } 25 | 26 | process.on('uncaughtException', function(e) { 27 | console.error(e.stack); 28 | 29 | /// Makesure error outputed before process exit. 30 | process.stderr.write('', function () { 31 | process.exit(1); 32 | }); 33 | }); 34 | 35 | function TransmitterStatusString(status) { 36 | switch (status) { 37 | case null: 38 | return '--'; 39 | case 0x00: 40 | return "OK"; 41 | case 0x81: 42 | return "Low battery"; 43 | case 0x83: 44 | return "Expired"; 45 | default: 46 | return status ? "Unknown: 0x" + status.toString(16) : '--'; 47 | } 48 | } 49 | 50 | 51 | function SensorStateString(state) { 52 | switch (state) { 53 | case 0x00: 54 | return 'None'; 55 | case 0x01: 56 | return 'Stopped'; 57 | case 0x02: 58 | return 'Warmup'; 59 | case 0x03: 60 | return 'Excess Noise'; 61 | case 0x04: 62 | return 'First calibration'; 63 | case 0x05: 64 | return 'Second calibration'; 65 | case 0x06: 66 | return 'OK'; 67 | case 0x07: 68 | return 'Needs calibration'; 69 | case 0x08: 70 | return 'Confused Calibration 1'; 71 | case 0x09: 72 | return 'Confused Calibration 2'; 73 | case 0x0a: 74 | return 'Needs More Calibration'; 75 | case 0x0b: 76 | return 'Sensor Failed Due to Counts Aberration'; 77 | case 0x0c: 78 | return 'Sensor Failed Due to Residual Aberration'; 79 | case 0x0d: 80 | return 'Outlier Calibration'; 81 | case 0x0e: 82 | return 'Needs More Calibration due to Outlier'; 83 | case 0x0f: 84 | return 'Sensor Session Ended'; 85 | case 0x10: 86 | return 'Sensor Failed Due To Unrecoverable Error'; 87 | case 0x11: 88 | return 'Transmitter Problem'; 89 | case 0x12: 90 | return 'Temporary Session Error'; 91 | case 0x13: 92 | return 'Sensor Failed 4'; 93 | case 0x14: 94 | return 'Sensor Failed 5'; 95 | case 0x15: 96 | return 'Sensor Failed 6'; 97 | case 0x16: 98 | return 'Sensor Failed Start'; 99 | case 0x80: 100 | return 'Calibration State - Start'; 101 | case 0x81: 102 | return 'Calibration State - Start Up'; 103 | case 0x82: 104 | return 'Calibration State - First of Two Calibrations Needed'; 105 | case 0x83: 106 | return 'Calibration State - High Wedge Display With First BG'; 107 | case 0x84: 108 | return 'Unused Calibration State - Low Wedge Display With First BG'; 109 | case 0x85: 110 | return 'Calibration State - Second of Two Calibrations Needed'; 111 | case 0x86: 112 | return 'Calibration State - In Calibration Transmitter'; 113 | case 0x87: 114 | return 'Calibration State - In Calibration Display'; 115 | case 0x88: 116 | return 'Calibration State - High Wedge Transmitter'; 117 | case 0x89: 118 | return 'Calibration State - Low Wedge Transmitter'; 119 | case 0x8a: 120 | return 'Calibration State - Linearity Fit Transmitter'; 121 | case 0x8b: 122 | return 'Calibration State - Out of Cal Due to Outlier Transmitter'; 123 | case 0x8c: 124 | return 'Calibration State - High Wedge Display'; 125 | case 0x8d: 126 | return 'Calibration State - Low Wedge Display'; 127 | case 0x8e: 128 | return 'Calibration State - Linearity Fit Display'; 129 | case 0x8f: 130 | return 'Calibration State - Session Not in Progress'; 131 | default: 132 | return state ? "Unknown: 0x" + state.toString(16) : '--'; 133 | } 134 | } 135 | 136 | const transmitter = new Transmitter(id, () => messages, alternateBluetooth); 137 | 138 | transmitter.on('calibrationData', (data) => { 139 | const util = require('util') 140 | console.log(util.inspect(data, false, null)) 141 | 142 | var fs = require('fs'); 143 | const calibrationData = JSON.stringify(data); 144 | fs.writeFile("/root/myopenaps/monitor/xdripjs/tx-calibration-data.json", calibrationData, function(err) { 145 | if(err) { 146 | console.log("Error while writing tx-calibration-data.json"); 147 | console.log(err); 148 | } 149 | process.exit(); 150 | }); 151 | 152 | }); 153 | 154 | transmitter.on('glucose', glucose => { 155 | //console.log('got glucose: ' + glucose.glucose); 156 | var d= new Date(glucose.readDate); 157 | var dsession= new Date(glucose.sessionStartDate); 158 | 159 | console.log(util.inspect(glucose, false, null)) 160 | var fs = require('fs'); 161 | const extra = [{ 162 | 'state_id': glucose.state, 163 | 'status_id': glucose.status, 164 | 'transmitterStartDate': glucose.transmitterStartDate, 165 | 'sessionStartDate': glucose.sessionStartDate, 166 | 'sessionStartDateEpoch': dsession.getTime() 167 | }]; 168 | const extraData = JSON.stringify(extra); 169 | const entry = [{ 170 | 'device': id, 171 | 'date': d.getTime(), 172 | 'dateString': d.toISOString(), 173 | //'sgv': Math.round(glucose.unfiltered/1000), 174 | 'sgv': glucose.glucose, 175 | 'direction': 'None', 176 | 'type': 'sgv', 177 | 'filtered': Math.round(glucose.filtered), 178 | 'unfiltered': Math.round(glucose.unfiltered), 179 | 'rssi': glucose.rssi, 180 | 'noise': "1", 181 | 'trend': glucose.trend, 182 | 'state': SensorStateString(glucose.state), 183 | 'status': TransmitterStatusString(glucose.status), 184 | 'glucose': Math.round(glucose.glucose) 185 | }]; 186 | const data = JSON.stringify(entry); 187 | 188 | fs.writeFile("/root/myopenaps/monitor/xdripjs/extra.json", extraData, function(err) { 189 | if(err) { 190 | console.log("Error while writing extra.json"); 191 | console.log(err); 192 | } 193 | fs.writeFile("/root/myopenaps/monitor/xdripjs/entry.json", data, function(err) { 194 | if(err) { 195 | console.log("Error while writing entry.json"); 196 | console.log(err); 197 | } 198 | // process.exit(); 199 | }); 200 | }); 201 | }); 202 | 203 | transmitter.on('sawTransmitter', data => { 204 | const util = require('util') 205 | console.log(util.inspect(data, false, null)) 206 | 207 | var fs = require('fs'); 208 | const sawTransmitter = JSON.stringify(data); 209 | fs.writeFile("/root/myopenaps/monitor/xdripjs/saw-transmitter.json", sawTransmitter, function(err) { 210 | if(err) { 211 | console.log("Error while writing saw-transmitter.json"); 212 | console.log(err); 213 | } 214 | }); 215 | }); 216 | 217 | transmitter.on('batteryStatus', data => { 218 | const util = require('util') 219 | console.log('got batteryStatus message inside logger msg: ' + JSON.stringify(data)); 220 | console.log(util.inspect(data, false, null)) 221 | 222 | var fs = require('fs'); 223 | const battery = JSON.stringify(data); 224 | fs.writeFile("/root/myopenaps/monitor/xdripjs/cgm-battery.json", battery, function(err) { 225 | if(err) { 226 | console.log("Error while writing cgm-battery.json"); 227 | console.log(err); 228 | } 229 | }); 230 | }); 231 | 232 | transmitter.on('version', data => { 233 | const util = require('util') 234 | console.log('got version message inside logger msg: ' + JSON.stringify(data)); 235 | console.log(util.inspect(data, false, null)) 236 | 237 | var fs = require('fs'); 238 | const version = JSON.stringify(data); 239 | fs.writeFile("/root/myopenaps/monitor/xdripjs/tx-version.json", version, function(err) { 240 | if(err) { 241 | console.log("Error while writing tx-version.json"); 242 | console.log(err); 243 | } 244 | }); 245 | }); 246 | 247 | transmitter.on('disconnect', process.exit); 248 | 249 | transmitter.on('messageProcessed', data => { 250 | console.log('logger message received: ' + JSON.stringify(data)); 251 | // console.log(util.inspect(data, false, null)) 252 | }); 253 | 254 | transmitter.on('backfillData', backfills => { 255 | //console.log('got glucosebackfill: ' + JSON.stringify(backfills)); 256 | 257 | var newEntries = []; 258 | var fs = require('fs'); 259 | for (var i = 0; i < backfills.length; ++i) { 260 | const backfill = backfills[i]; 261 | //console.log('processing backfill entry:' + JSON.stringify(backfill)); 262 | const entry = { 263 | 'device': id, 264 | 'date': backfill.time, 265 | 'dateString': new Date(backfill.time).toISOString(), 266 | 'sgv': backfill.glucose, 267 | 'direction': 'None', 268 | 'type': 'sgv', 269 | 'trend': backfill.trend, 270 | 'state': SensorStateString(backfill.type), 271 | 'glucose': Math.round(backfill.glucose) 272 | }; 273 | //console.log('resulting backfill entry:' + JSON.stringify(entry)); 274 | newEntries.push(entry); 275 | } 276 | console.log('New backfill entries:' + JSON.stringify(newEntries)); 277 | fs.readFile('/root/myopenaps/monitor/xdripjs/entry-backfill2.json', function (err, data) { 278 | var json = []; 279 | if (data && data.length > 0) { 280 | json = JSON.parse(data); 281 | } 282 | json = json.concat(newEntries); 283 | console.log("full backfill entries to upload: " + JSON.stringify(json)); 284 | fs.writeFile("/root/myopenaps/monitor/xdripjs/entry-backfill2.json", JSON.stringify(json), function(err) { 285 | if(err) { 286 | console.log("Error while writing tx-version.json"); 287 | console.log(err); 288 | } 289 | }); 290 | }); 291 | }); 292 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Logger 2 | 3 | [![Join the chat at https://gitter.im/thebookins/xdrip-js](https://badges.gitter.im/thebookins/xdrip-js.svg)](https://gitter.im/thebookins/xdrip-js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | *Please note this project is neither created nor backed by Dexcom, Inc.* 6 | *Logger is not a product. Logger comes with no warranty or official support. Anyone using Logger is doing so at their own risk and must take responsibility for their own safety. The use of Logger for therapy is not FDA approved and comes with inherent risks.* 7 | 8 | Logger is a Bash (shell) based Dexcom g5 / g6 glucose pre-processor for OpenAPS. Logger runs on the OpenAPS rig and connects to the g5 or g6 transmitter via bluetooth, waits for the first bg, logs a json entry record, processes the entry record based on calibrations, updates NightScout and OpenAPS indepently, then exits. Logger is a wrapper shell script to xdrip-js that is called from cron every minute. The user interface is a mixture between unix command line scripts and NightScout. The current Logger features are below: 9 | 10 | * Preparation and sending of the blood glucose data to Nightscout and to OpenAPS. 11 | * Alternate Bluetooth connection - allows Logger to run alongside one other CGM app (besides the receiver). With this setting set to true, Logger can run alongside Xdrip+ or the Dexcom App. 12 | * Offline mode - Logger runs on the rig and sends bg data directly to openaps through via xdripAPS. Logger queues up NS updates while internet is down and fills in the gaps when internet is restored. 13 | * Reset Transmitter - Use the following command, wait > 10 minutes, and your expired transmitter is new again! Careful, though it will reset everything on your transmitter including your current session. Note this feature is only available via the command line. ```cgm-reset``` 14 | 15 | * Stop Sensor - Use the following command, wait > 5 minutes, and your current sensor session will stop. Use this command before changing out your sensor so that Logger will stop sending glucose information temporarily (while the sensor is stopped) This feature is also available via the command line only. ```cgm-stop``` 16 | 17 | * Start Sensor - Use the following command, wait > 5 minutes, and your sensor session will start. Use this command after inserting your sensor so that Logger will start the process for sending glucose information. This feature is also available via the command line and you can also do it via Nightscout as a BG Treatment, entry type of Sensor Start. ```cgm-start``` 18 | 19 | * Set Transmitter - Use the following command, wait > 5 minutes, and your new transmitter id will be used. This feature is only available if using the xdripjs.json configuration file & not calling Logger with the transmitter id as an argument. ```cgm-transmitter``` 20 | 21 | * Calibration via linear least squares regression (LSR) (similar to xdrip plus) 22 | * Calibrations must be input into Nightscout as BG Check treatments or command line ```calibrate bg_value```. 23 | * Logger will not calculate or send any BG data out unless at least one calibration has been done in Nightscout. Calibration is not required for a g6 when using no-calibration by sending it a valid sensor serial code in the cgm-start message. 24 | * LSR calibration only comes into play after 3 or more calibrations. When there one or two calibrations, single point linear calibration is used. 25 | * The calibration cache will be cleared for the first 15 minutes after a Nightscout "CGM Sensor Insert" has been posted as Nightscout treatment. 26 | * After 15 minutes, BG data will only be sent out after at least one calibration has been documented in Nightscout. 27 | 28 | * fakemeter - Additional offline ability where Logger sends the blood glucose to the pump. To enable, simply go into the pump and add a meter with id 000000 through the Utilities -> Meter Options menu 29 | 30 | # Warning! 31 | 32 | Logger LSR calibration is a new feature as of Feb/2018. Only those who closely monitor and check blood glucose and regularly review the Logger logfiles should use this program at this time. 33 | * `/var/log/openaps/logger-loop.log` 34 | * `/var/log/openaps/cgm.csv` 35 | * `/root/myopenaps/monitor/xdripjs/calibrations.csv` - the current list of calibrations (unfiltered, BG check, datetime, BG Check ID) 36 | * `/root/myopenaps/monitor/xdripjs/calibration-linear.json` - the current calibration values (slope, yIntercept). Please note that other fields in this file are for informational purposes at this time. 37 | 38 | [![Join the chat at https://gitter.im/thebookins/xdrip-js](https://badges.gitter.im/thebookins/xdrip-js.svg)](https://gitter.im/thebookins/xdrip-js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 39 | 40 | *Please note this project is neither created nor backed by Dexcom, Inc.* 41 | ## Prerequisites 42 | * Openaps must be installed using the CGM method of xdrip. 43 | * Logger does not currently support token-based authentication with Nightscout 44 | 45 | Update node version. Follow these steps in this order. 46 | 47 | Set Nightscout environment variables in ~/.bash_profile. Make sure the following 4 lines are in this file. If not carefully add them paying close attention the values (xxxx is your hashed Nightscout API_SECRET and yyyy is your Nightscout URL): 48 | ``` 49 | API_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxx 50 | export API_SECRET 51 | NIGHTSCOUT_HOST=https://yyyyyyy.herokuapp.com 52 | export NIGHTSCOUT_HOST 53 | ``` 54 | 55 | For the edison/explorer board (jubilinux), the version of Node that ships with jubilinux is old (v0.10.something). Here are the instructions for updating the edison/explorer rig's Node: 56 | ``` 57 | sudo apt-get remove nodered -y 58 | sudo apt-get remove nodejs nodejs-legacy -y 59 | sudo apt-get remove npm -y # if you installed npm 60 | sudo curl -sL https://deb.nodesource.com/setup_8.x | sudo bash - 61 | sudo apt-get install nodejs -y 62 | 63 | cd ~/src/Logger && npm rebuild # if Logger exists already and you are upgrading node from 6.x to 8.x 64 | ``` 65 | 66 | If you are using the RPI zero hat, the version of Node that ships with jubilinux also needs updating. Here are the instructions for updating the rpi zero hat's Node: 67 | ``` 68 | mkdir node && cd node 69 | wget https://nodejs.org/dist/v8.10.0/node-v8.10.0-linux-armv6l.tar.xz 70 | tar -xf node-v8.10.0-linux-armv6l.tar.xz 71 | cd *6l 72 | sudo cp -R * /usr/local/ 73 | # add /usr/local/bin to PATH env variable in .bash_profile 74 | node -v 75 | # should show 8.10.0 at this point 76 | ``` 77 | 78 | ## Installation 79 | ``` 80 | cd ~/src 81 | git clone https://github.com/xdrip-js/Logger.git 82 | cd Logger 83 | sudo npm run global-install 84 | sudo apt-get install bluez-tools 85 | ``` 86 | 87 | Set your transmitter (replace 403BX6 with your actual 6 digit transmitter serial number)... 88 | ``` 89 | cgm-transmitter 403BX6 90 | ``` 91 | 92 | Add cron job entry ... 93 | ``` 94 | * * * * * cd /root/src/Logger && ps aux | grep -v grep | grep -q Logger || /usr/local/bin/Logger >> /var/log/openaps/logger-loop.log 2>&1 95 | ``` 96 | 97 | ## Logger Configuration Options: 98 | 99 | You can edit the following options values in the configuration file (~/myopenaps/xdripjs.json) 100 | 101 | {"transmitter_id":"8XXXXX"}, Required - 6 character tx serial number (i.e. 403BX6). Should be set using cgm-transmitter cmd. 102 | {"sensor_code":"1234"}, Optional - Sensor code used for G6 only. Start a transmitter using this to use the G6 no-calibration mode. If set to a non-empty string, calibrations are not sent to the transmitter. To set a new sensor code you must set it using the cgm-start cmd or NS. 103 | {"mode":"native-calibrates-lsr"}, Optional - if you specify "read-only" then Logger still sends BG values to OpenAPS, but all start/stop/calibrations must be done by the receiver, the Dexcom App, or XdripPlus. The default is "native-calibrates-lsr" then it will use the Dexcom algorithms, unless not available, and it will also calibrate the local LSR algorithm based on the values of the Dexcom algorithm, every 6 hours. "native-calibrates-lsr" is useful to allow expired sessions and extended sensors to automatically use recent calibration data. 104 | 105 | {"pump_units":"mg/dl"}, Optional - pumpUnits default is "mg/dl" 106 | {"fake_meter_id":"000000"}, Optional - meterid for fakemeter sending glucose records to pump, default is "000000" 107 | {"alternate_bluetooth_channel":true or false} Optional - Default is true. If set to true then Logger uses the alternate channel to connect to the Dexcom transmitter. If set to true the receiver cannot be used. However, when set to true, either the Xdip Plus android app or the Dexcom Iphone app can be used alongside Logger. Keep in mind that there is a higher chance for bluetooth conflict when connecting to the transmitter with both channels. You may be able to avoid some reconnects by keeping the rig and the phone app physically separated by several inches. 108 | {"watchdog":true or false}, Optional - Default is true. If set to true then Logger will automatically reboot the rig to resolve bluetooth issues if no glucose is seen from the transmitter in more than 14 minutes. 109 | {"utc":true or false}, Optional - Default is true. If set to true then Logger will assume UTC date/time strings coming from the curls to NightScout. 110 | {"fakemeter_only_offline":true or false}, Optional - Default is false. If set to false then Logger will not attempt to call fakemeter unless the rig is offline. 111 | 112 | ## Getting Started 113 | First, make sure you've checked the Prerequisites above and completed the Installation steps. Afterwords, perform the following steps: 114 | 115 | Review the Logger logfile to ensure the proccess is proceeding without errors. Run the following command on the rig and keep it running for debugging purposes for the first time you go through the procedures below: 116 | 117 | ``` 118 | tail -f /var/log/openaps/logger-loop.log 119 | ``` 120 | 121 | Insert the new Dexcom g5 or g6 sensor. Notify Logger of this insertion by using Nightscout Treatment CGM Sensor Insert 122 | 123 | Start the new Dexcom g5 sensor by using one of the two methods (Nightscout Treatment CGM Sensor Start) or (run ```cgm-start``` from the command line) 124 | 125 | If starting a g6, specify the sensor serial code by putting the 4 digit code in the Nightscout Sensor Start Note or passing the the command line as follows: ```cgm-start -c 1234``` where 1234 is the sensor serial code. 126 | 127 | Within 15 minutes, the sensor state should show "Warmup" in Nightscout and in the Logger log. At this point, you have 2 options: 128 | 129 | Prefered: Wait to calibrate until the 2 hour warmup period is complete and the sensor state in NS shows "First Calibration". 130 | Calibrate again once the sensor state in NS shows "Second Calibration" 131 | 132 | Not Prefered / Advanced mode: Calibrate after > 15 minutes since CGM start. 133 | 134 | Calibrate by using one of the two methods (Nightscout Treatment BG Check and put calibrated glucose in the Glucose Reading field) or (run ```calibrate bgvalue``` from the command line) 135 | 136 | After calibration(s), you should see BG values in Nightscout and in the log. 137 | 138 | # Troubleshooting 139 | ## G5 Tx unfiltered / filtered values showing 0 140 | 141 | If both unfiltered and filtered values are showing up as 0 in the Logger logfile, then you may be able to solve the problem by doing a ```cgm-reset```. Note: This will reset the session and you will lose any transmitter stored calibrations so this technique is probably best used when unfiltered is 0 upon new sensor insertion. 142 | 143 | ## Tx Communication Timing Out Issues 144 | 145 | If timing out, recheck the configuration of the transmitter id, make sure the bt-device command is available, ensure there are no conflicting bluetooth connections on the same channel (i.e. Dexcom App, Receiver, or XDrip+). 146 | 147 | 148 | Since the timer only allows communications for a few seconds every 5 minutes, isolation of any timeout issues are key. The following are some suggestions: 149 | 1) Turn off Logger and receiver, run Xdrip+ and see if it can connect to the tx. If Xdrip+ can connect, then it may be a rig issue. If Xdrip+ cannot connect, then it's mostly isolated to a tx issue. 150 | 2) Run the following command. If it fails for any reason, then the install of bluez-tools may have an issue. If bluez-tools is not installed, then bt timeouts will likely be the result. ```bt-device -l``` 151 | 3) Check your network log. BT PAN may be trying to do something and restarting bluetoothd. Check /var/log/openaps/network.log and any bluetooth log (if it exists) in that directory. Also, check for bluetooth errors in /var/log/syslog as well. 152 | 4) Turn off every other possible dexcom connection and try connecting with the tx with the official Dexcom app. This will only work if you have a non-expired tx or have successfully reset it earlier. 153 | 5) Try a different transmitter (if you have one). If this works, then the other tx has an issue, usually battery related. 154 | 6) Try a different rig (if you have one). If this works, then the other rig or it's install/configuration is likely the culprit. 155 | 7) If on an RPI, check the mode in /etc/bluetooth/main.conf. At least one user found that setting mode to Controller mode resolved the timeouts. 156 | 157 | If you have network connectivity on the rig, but BG values are not showing up on NightScout, then run the following command which should retrieve the last BG Check Treatment posted to NightScout. Review any errors that the command returns and re-check your NIGHTSCOUT_HOST and API_SECRET environment variables. 158 | ``` 159 | curl --compressed -m 30 -H "API-SECRET: ${API_SECRET}" "${NIGHTSCOUT_HOST}/api/v1/treatments.json?find\[eventType\]\[\$regex\]=Check&count=1" 160 | ``` 161 | ## Checking Sensor Noise Levels 162 | To check recent reported BG noise levels, run the command line utility ```cgm-noise``` 163 | 164 | ## Install issues 165 | If during installation you get stuck after running this command ```sudo npm run global-install``` at a point starting ```fetchMetadata``` it suggests you have an issue connecting to the git reposistory. E.g.: 166 | ``` 167 | > Logger@1.2.4 global-install /root/src/Logger 168 | > rm -rf ./node-modules/xdrip-js && ./bin/upgrade-node.sh && npm install && ./bin/logger-setup.sh 169 | 170 | Node version already at v8 - good to go 171 | [ ................] / fetchMetadata: sill install loadAllDepsIntoIdealTree 172 | ``` 173 | To fix this, run the following ```git config --global url."https://".insteadOf git://``` which points the downloader to the https:// instance and run ```sudo npm run global-install``` again and carry on from that point. 174 | 175 | If this doesn't work, delete the Logger folder and all it's contents ```rm -rf /root/src/Logger``` and then re-run the installation from the start. 176 | 177 | -------------------------------------------------------------------------------- /xdrip-get-entries.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | common_funcs="/root/src/Logger/bin/logger-common-funcs.sh" 4 | if [ ! -e $common_funcs ]; then 5 | echo "ERROR: Failed to run logger-common-funcs.sh. Is Logger correctly installed?" 6 | exit 1 7 | fi 8 | source $common_funcs 9 | 10 | 11 | SECONDS_IN_10_DAYS=864000 12 | SECONDS_IN_1_DAY=86400 13 | SECONDS_IN_7_DAYS=604800 14 | SECONDS_IN_30_MINUTES=1800 15 | 16 | CONF_DIR="${HOME}/myopenaps" 17 | OLD_LDIR="${HOME}/myopenaps/monitor/logger" 18 | treatmentsFile="${LDIR}/treatments-backfill.json" 19 | lastEntryFile="${LDIR}/last-entry.json" 20 | calibrationFile="${LDIR}/calibration-linear.json" 21 | calCacheFile="${LDIR}/calibrations.csv" 22 | xdripMessageFile="${LDIR}/xdrip-js-messages.json" 23 | calibrationMessageFile="${LDIR}/calibration-xdrip-js-messages.json" 24 | sentLoggerCalibrationToTx=false 25 | CALFILE="${LDIR}/calibration.json" 26 | 27 | main() 28 | { 29 | log "Starting Logger" 30 | 31 | check_dirs 32 | 33 | # Cmd line args - transmitter $1 is 6 character tx serial number 34 | transmitter=$1 35 | if [ -z "$transmitter" ]; then 36 | # check config file 37 | transmitter=$(cat ${CONF_DIR}/xdripjs.json | jq -M -r '.transmitter_id') 38 | fi 39 | if [ -z "$transmitter" ] || [ "$transmitter" == "null" ]; then 40 | log "ERROR: No transmitter id set!; exiting" 41 | exit 42 | fi 43 | 44 | txType="g5" 45 | 46 | if [[ $transmitter == 8* ]]; then 47 | txType="g6" 48 | fi 49 | 50 | cmd_line_mode=$2 51 | if [ -z "$cmd_line_mode" ]; then 52 | # check config file 53 | cmd_line_mode=$(cat ${CONF_DIR}/xdripjs.json | jq -M -r '.mode') 54 | if [ -z "$cmd_line_mode" ] || [ "$cmd_line_mode" == "null" ]; then 55 | cmd_line_mode="" 56 | fi 57 | fi 58 | 59 | pumpUnits=$3 60 | if [ -z "$pumpUnits" ]; then 61 | # check config file 62 | pumpUnits=$(cat ${CONF_DIR}/xdripjs.json | jq -M -r '.pump_units') 63 | if [ -z "$pumpUnits" ] || [ "$pumpUnits" == "null" ]; then 64 | pumpUnits="mg/dl" 65 | fi 66 | fi 67 | 68 | meterid=$4 69 | if [ -z "$meterid" ]; then 70 | # check config file 71 | meterid=$(cat ${CONF_DIR}/xdripjs.json | jq -M -r '.fake_meter_id') 72 | if [ -z "$meterid" ] || [ "$meterid" == "null" ]; then 73 | meterid="000000" 74 | fi 75 | fi 76 | 77 | # check config file 78 | sensorCode=$(cat ${CONF_DIR}/xdripjs.json | jq -M -r '.sensor_code') 79 | if [ -z "$sensorCode" ] || [ "$sensorCode" == "null" ]; then 80 | sensorCode="" 81 | fi 82 | 83 | watchdog=$(cat ${CONF_DIR}/xdripjs.json | jq -M -r '.watchdog') 84 | if [ -z "$watchdog" ] || [ "$watchdog" == "null" ]; then 85 | watchdog=true 86 | fi 87 | log "Parameter (watchdog): $watchdog" 88 | 89 | utc=$(cat ${CONF_DIR}/xdripjs.json | jq -M -r '.utc') 90 | if [ -z "$utc" ] || [ "$utc" == "null" ]; then 91 | utc=true 92 | fi 93 | log "Parameter (utc): $utc" 94 | 95 | auto_sensor_restart=$(cat ${CONF_DIR}/xdripjs.json | jq -M -r '.auto_sensor_restart') 96 | if [ -z "$auto_sensor_restart" ] || [ "$auto_sensor_restart" == "null" ]; then 97 | auto_sensor_restart=false 98 | fi 99 | log "Parameter (auto_sensor_restart): $auto_sensor_restart" 100 | 101 | 102 | fakemeter_only_offline=$(cat ${CONF_DIR}/xdripjs.json | jq -M -r '.fakemeter_only_offline') 103 | if [ -z "$fakemeter_only_offline" ] || [ "$fakemeter_only_offline" == "null" ]; then 104 | fakemeter_only_offline=false 105 | fi 106 | 107 | log "Parameter (fakemeter_only_offline): $fakemeter_only_offline" 108 | 109 | alternateBluetoothChannel=$(cat ${CONF_DIR}/xdripjs.json | jq -M -r '.alternate_bluetooth_channel') 110 | if [ -z "$alternateBluetoothChannel" ] || [ "$alternateBluetoothChannel" == "null" ]; then 111 | alternateBluetoothChannel=false 112 | fi 113 | 114 | log "Parameter (alternateBluetoothChannel): $alternateBluetoothChannel" 115 | log "Parameter (transmitter): $transmitter" 116 | 117 | id2=$(echo "${transmitter: -2}") 118 | id="Dexcom${id2}" 119 | rig="openaps://$(hostname)" 120 | glucoseType="unfiltered" 121 | noiseSend=4 # default heavy 122 | UTCString=" -u " 123 | lastGlucose=0 124 | lastGlucoseDate=0 125 | lastSensorInsertDate=0 126 | variation=0 127 | messages="" 128 | ns_url="${NIGHTSCOUT_HOST}" 129 | METERBG_NS_RAW="${LDIR}/meterbg_ns_raw.json" 130 | battery_check="No" # default - however it will be changed to Yes every 12 hours 131 | sensitivty=0 132 | 133 | epochdate=$(date +'%s') 134 | epochdatems=$(date +'%s%3N') 135 | dateString=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") 136 | 137 | getTxVersion # reads the json file that stores the latest known tx firmware version 138 | initialize_mode # call now and after getting status from tx 139 | initialize_messages 140 | check_environment 141 | check_utc 142 | 143 | check_last_entry_values 144 | 145 | check_last_glucose_time_smart_sleep 146 | 147 | # clear out prior curl or tx responses 148 | rm -f $METERBG_NS_RAW 149 | rm -f ${LDIR}/entry.json 150 | 151 | check_sensor_change 152 | check_sensitivity 153 | 154 | check_send_battery_status 155 | check_sensor_start 156 | check_sensor_stop 157 | 158 | # begin calibration logic - look for calibration from NS, use existing calibration or none 159 | maxDelta=30 160 | found_meterbg=false 161 | check_cmd_line_calibration 162 | check_pump_history_calibration 163 | check_ns_calibration 164 | check_messages 165 | 166 | remove_dexcom_bt_pair 167 | compile_messages 168 | call_logger 169 | epochdate=$(date +'%s') 170 | epochdatems=$(date +'%s%3N') 171 | dateString=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") 172 | capture_entry_values 173 | checkif_fallback_mode 174 | 175 | log "Mode = $mode" 176 | if [[ "$mode" != "expired" ]]; then 177 | initialize_calibrate_bg 178 | else 179 | check_last_calibration 180 | fi 181 | set_entry_fields 182 | 183 | check_variation 184 | check_native_calibrates_lsr 185 | check_tx_calibration 186 | 187 | #call after posting to NS OpenAPS for not-expired mode 188 | if [ "$mode" == "expired" ]; then 189 | calculate_calibrations 190 | fi 191 | 192 | check_recent_sensor_insert 193 | 194 | 195 | readLastState 196 | if [ "$mode" == "expired" ]; then 197 | apply_lsr_calibration 198 | fi 199 | 200 | # necessary for not-expired mode - ok for both modes 201 | cp -p ${LDIR}/entry.json ${LDIR}/entry-xdrip.json 202 | 203 | process_delta # call for all modes 204 | calculate_noise # necessary for all modes 205 | process_if_lsr_calibrates_native 206 | 207 | fake_meter 208 | 209 | if [ "$state" != "Stopped" ] || [ "$mode" == "expired" ]; then 210 | log "Posting glucose record to xdripAPS / OpenAPS" 211 | if [ -e "${LDIR}/entry-backfill2.json" ] ; then 212 | local numBackfills=$(jq '. | length' ${LDIR}/entry-backfill2.json) 213 | if [ $(bc <<< "$numBackfills > 6") -eq 1 ]; then 214 | # more than 30 minutes of missed/backfilled glucose values 215 | postAnnouncementToNSAdder="Backfilled $numBackfills glucose values" 216 | fi 217 | /usr/local/bin/cgm-post-xdrip ${LDIR}/entry-backfill2.json 218 | fi 219 | /usr/local/bin/cgm-post-xdrip ${LDIR}/entry-xdrip.json 220 | fi 221 | 222 | post-nightscout-with-backfill 223 | cp -p ${LDIR}/entry-xdrip.json $lastEntryFile 224 | 225 | if [ "$mode" != "expired" ]; then 226 | log "Calling expired tx lsr calcs (after posting) -allows mode switches / comparisons" 227 | calculate_calibrations 228 | apply_lsr_calibration 229 | fi 230 | 231 | check_battery_status 232 | 233 | log_cgm_csv 234 | 235 | process_announcements 236 | post_cgm_ns_pill 237 | 238 | saveLastState 239 | 240 | remove_dexcom_bt_pair 241 | log "Completed Logger" 242 | echo 243 | } 244 | 245 | function validNumber() 246 | { 247 | local num=$1 248 | case ${num#[-+]} in 249 | *[!0-9.]* | '') echo false ;; 250 | * ) echo true ;; 251 | esac 252 | } 253 | 254 | 255 | function validBG() 256 | { 257 | local bg=$1 258 | local valid="false" 259 | 260 | if [ "$(validNumber $bg)" == "true" ]; then 261 | if [ $(bc -l <<< "$bg >= 20") -eq 1 -a $(bc -l <<< "$bg < 500") -eq 1 ]; then 262 | valid="true" 263 | fi 264 | fi 265 | 266 | echo $valid 267 | } 268 | 269 | 270 | function check_dirs() { 271 | 272 | if [ ! -d ${LDIR} ]; then 273 | if [ -d ${OLD_LDIR} ]; then 274 | mv ${OLD_LDIR} ${LDIR} 275 | fi 276 | fi 277 | mkdir -p ${LDIR} 278 | mkdir -p ${LDIR}/old-calibrations 279 | } 280 | 281 | # This func takes an arg list of value name pairs creating a simple json string 282 | # What's different about this vs jq is that this function will ignore 283 | # any value/name pair where the variable value name is null or blank 284 | # it also automatically handles quotes around variable values with strings 285 | # and doesn't include quotes for those without strings 286 | 287 | function build_json() { 288 | local __result="{" 289 | 290 | args=("$@") 291 | for (( i=0; i < ${#}; i+=2 )) 292 | do 293 | local __key=${args[$i]} 294 | local __value=${args[$i+1]} 295 | #echo "key=$__key, value=$__value" 296 | local __len=${#__value} 297 | if [ $__len -gt 0 ]; then 298 | if [ $(echo "$__value" | grep -cE "^\-?([0-9]+)(\.[0-9]+)?$") -gt 0 ]; then 299 | # must be a number 300 | __result="$__result\"$__key\":$__value," 301 | else 302 | # must be a string 303 | __result="$__result\"$__key\":\"$__value\"," 304 | fi 305 | fi 306 | done 307 | # remove comma on last value/name pair 308 | __result="${__result::-1}}" 309 | echo $__result 310 | } 311 | 312 | function readLastState 313 | { 314 | lastState=$(cat ${LDIR}/Logger-last-state.json | jq -M '.[0].state') 315 | lastState="${lastState%\"}" 316 | lastState="${lastState#\"}" 317 | log "readLastState: lastState=$lastState" 318 | } 319 | 320 | function saveLastState 321 | { 322 | log "saveLastState, state=$state" 323 | echo "[{\"state\":\"${state}\",\"txId\":\"${transmitter}\"}]" > ${LDIR}/Logger-last-state.json 324 | } 325 | 326 | function log 327 | { 328 | echo -e "$(date +'%m/%d %H:%M:%S') $*" 329 | } 330 | 331 | function fake_meter() 332 | { 333 | if [[ "$fakemeter_only_offline" == true && !$(~/src/Logger/bin/logger-online.sh) ]]; then 334 | log "Not running fakemeter because fakemeter_only_offline=true and not offline" 335 | return 336 | fi 337 | 338 | if [ -e "/usr/local/bin/fakemeter" ]; then 339 | if [ -d ~/myopenaps/plugins/once ]; then 340 | scriptf=~/myopenaps/plugins/once/run_fakemeter.sh 341 | log "Scheduling fakemeter run once at end of next OpenAPS loop to send BG of $calibratedBG to pump via meterid $meterid" 342 | echo "#!/bin/bash" > $scriptf 343 | echo "fakemeter -m $meterid $calibratedBG" >> $scriptf 344 | chmod +x $scriptf 345 | else 346 | if ! listen -t 4s >& /dev/null ; then 347 | log "Sending BG of $calibratedBG to pump via meterid $meterid" 348 | fakemeter -m $meterid $calibratedBG 349 | else 350 | log "Timed out trying to send BG of $calibratedBG to pump via meterid $meterid" 351 | fi 352 | fi 353 | fi 354 | } 355 | 356 | 357 | # Check required environment variables 358 | function check_environment 359 | { 360 | source ~/.bash_profile 361 | cd ~/src/Logger 362 | export API_SECRET 363 | export NIGHTSCOUT_HOST 364 | if [ "$API_SECRET" = "" ]; then 365 | log "API_SECRET environment variable is not set" 366 | log "Make sure the two lines below are in your ~/.bash_profile as follows:\n" 367 | log "API_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxx # where xxxx is your hashed NS API_SECRET" 368 | log "export API_SECRET\n\nexiting\n" 369 | state_id=0x21 370 | state="API_SECRET Not Set" ; stateString=$state ; stateStringShort=$state 371 | post_cgm_ns_pill 372 | exit 373 | fi 374 | if [ "$NIGHTSCOUT_HOST" = "" ]; then 375 | log "NIGHTSCOUT_HOST environment variable is not set" 376 | log "Make sure the two lines below are in your ~/.bash_profile as follows:\n" 377 | log "NIGHTSCOUT_HOST=https://xxxx # where xxxx is your hashed Nightscout url" 378 | log "export NIGHTSCOUT_HOST\n\nexiting\n" 379 | state_id=0x22 380 | state="NIGHTSCOUT_HOST Not Set" ; stateString=$state ; stateStringShort=$state 381 | post_cgm_ns_pill 382 | exit 383 | fi 384 | 385 | type bt-device 2> /dev/null || log "Error: bt-device is not found. Use sudo apt-get install bluez-tools" 386 | 387 | } 388 | 389 | function ClearCalibrationInput() 390 | { 391 | if [ -e $calCacheFile ]; then 392 | cp $calCacheFile "${LDIR}/old-calibrations/calibrations.csv.$(date +%Y%m%d-%H%M%S)" 393 | rm $calCacheFile 394 | fi 395 | } 396 | 397 | # we need to save the last calibration for meterbgid checks, throw out the rest 398 | function ClearCalibrationInputOne() 399 | { 400 | if [ -e $calCacheFile ]; then 401 | howManyLines=$(wc -l $calCacheFile | awk '{print $1}') 402 | if [ $(bc <<< "$howManyLines > 1") -eq 1 ]; then 403 | cp $calCacheFile "${LDIR}/old-calibrations/calibrations.csv.$(date +%Y%m%d-%H%M%S)" 404 | tail -1 $calCacheFile > ${LDIR}/calibrations.csv.new 405 | rm $calCacheFile 406 | mv ${LDIR}/calibrations.csv.new $calCacheFile 407 | fi 408 | fi 409 | } 410 | 411 | function ClearCalibrationCache() 412 | { 413 | if [ -e $calibrationFile ]; then 414 | cp $calibrationFile "${LDIR}/old-calibrations/calibration-linear.$(date +%Y%m%d-%H%M%S)" 415 | rm $calibrationFile 416 | fi 417 | } 418 | 419 | # check utc command line to begin with and use UTC flag for any curls 420 | function check_utc() 421 | { 422 | if [[ "$utc" == true ]]; then 423 | UTCString=" -u " 424 | log "Using UTCString $UTCString" 425 | else 426 | UTC="" 427 | log "Not Using UTCString" 428 | fi 429 | } 430 | 431 | function log_status_csv() 432 | { 433 | file="/var/log/openaps/cgm-status.csv" 434 | if [ ! -f $file ]; then 435 | echo "epochdate,datetime,status,voltagea,voltageb,resist,runtime,temperature" > $file 436 | fi 437 | echo "${epochdate},${datetime},${tx_status},${voltagea},${voltageb},${resist},${runtime} days,${temperature} celcuis" >> $file 438 | } 439 | 440 | #called after a battery status update was sent and logger got a response 441 | function check_battery_status() 442 | { 443 | 444 | file="${LDIR}/cgm-battery.json" 445 | voltagea=$(jq ".voltagea" $file) 446 | voltageb=$(jq ".voltageb" $file) 447 | resist=$(jq ".resist" $file) 448 | runtime=$(jq ".runtime" $file) 449 | temperature=$(jq ".temperature" $file) 450 | batteryTimestamp=$(date +%s%3N -r $file) 451 | 452 | if [ "$battery_check" == "Yes" ]; then 453 | tx_status=$(jq ".status" $file) 454 | battery_msg="tx_status=$tx_status, voltagea=$voltagea, voltageb=$voltageb, resist=$resist, runtime=$runtime days, temp=$temperature celcius" 455 | 456 | echo "[{\"enteredBy\":\"Logger\",\"eventType\":\"Note\",\"notes\":\"Battery $battery_msg\"}]" > ${LDIR}/cgm-battery-status.json 457 | /usr/local/bin/cgm-post-ns ${LDIR}/cgm-battery-status.json treatments && (echo; log "Upload to NightScout of battery status change worked") || (echo; log "Upload to NS of battery status change did not work") 458 | log_status_csv 459 | fi 460 | } 461 | 462 | 463 | function check_send_battery_status() 464 | { 465 | file="${LDIR}/cgm-battery.json" 466 | 467 | if [ -e $file ]; then 468 | if test `find $file -mmin +720` 469 | then 470 | battery_check="Yes" 471 | fi 472 | else 473 | touch $file 474 | battery_check="Yes" 475 | fi 476 | 477 | if [ "$battery_check" == "Yes" ]; then 478 | touch $file 479 | battery_date=$(date +'%s%3N') 480 | batteryJSON="[{\"date\": ${battery_date}, \"type\": \"BatteryStatus\"}]" 481 | versionJSON="[{\"date\": ${battery_date}, \"type\": \"VersionRequest\"}]" 482 | log "Sending Message to Transmitter to request battery and version status" 483 | fi 484 | } 485 | 486 | function check_sensor_stop() 487 | { 488 | if [ "$mode" == "read-only" ]; then 489 | return 490 | fi 491 | 492 | file="${LDIR}/nightscout_sensor_stop_treatment.json" 493 | rm -f $file 494 | curl --compressed -m 30 -H "API-SECRET: ${API_SECRET}" "${NIGHTSCOUT_HOST}/api/v1/treatments.json?find\[created_at\]\[\$gte\]=$(date -d "3 hours ago" --iso-8601=seconds $UTCString )&find\[eventType\]\[\$regex\]=Sensor.Stop&count=1" 2>/dev/null > $file 495 | if [ $? == 0 ]; then 496 | len=$(jq '. | length' $file) 497 | index=$(bc <<< "$len - 1") 498 | 499 | if [ $(bc <<< "$index >= 0") -eq 1 ]; then 500 | createdAt=$(jq ".[$index].created_at" $file) 501 | createdAt="${createdAt%\"}" 502 | createdAt="${createdAt#\"}" 503 | if [ ${#createdAt} -ge 8 ]; then 504 | touch ${LDIR}/nightscout-treatments.log 505 | if ! cat ${LDIR}/nightscout-treatments.log | egrep "$createdAt"; then 506 | stop_date=$(date "+%s%3N" -d "$createdAt") 507 | log "Processing sensor stop retrieved from Nightscout - stopdate = $createdAt" 508 | # comment out below line for testing sensor stop without actually sending tx message 509 | stopJSON="[{\"date\":\"${stop_date}\",\"type\":\"StopSensor\"}]" 510 | log "stopJSON = $stopJSON" 511 | # below done so that next time the egrep returns positive for this specific message and the log reads right 512 | echo "Already Processed Sensor Stop Message from Nightscout at $createdAt" >> ${LDIR}/nightscout-treatments.log 513 | # Always clear LSR cache for any new firmware g6 start / stop 514 | if [ "$(newFirmware $tx_version)" == "true" ]; then 515 | ClearCalibrationInput 516 | ClearCalibrationCache 517 | fi 518 | fi 519 | fi 520 | fi 521 | fi 522 | } 523 | 524 | function check_sensor_start() 525 | { 526 | if [ "$mode" == "read-only" ]; then 527 | return 528 | fi 529 | 530 | file="${LDIR}/nightscout_sensor_start_treatment.json" 531 | rm -f $file 532 | curl --compressed -m 30 -H "API-SECRET: ${API_SECRET}" "${NIGHTSCOUT_HOST}/api/v1/treatments.json?find\[created_at\]\[\$gte\]=$(date -d "3 hours ago" --iso-8601=seconds $UTCString )&find\[eventType\]\[\$regex\]=Sensor.Start&count=1" 2>/dev/null > $file 533 | if [ $? == 0 ]; then 534 | len=$(jq '. | length' $file) 535 | index=$(bc <<< "$len - 1") 536 | 537 | if [ $(bc <<< "$index >= 0") -eq 1 ]; then 538 | createdAt=$(jq ".[$index].created_at" $file) 539 | createdAt="${createdAt%\"}" 540 | createdAt="${createdAt#\"}" 541 | if [ ${#createdAt} -ge 8 ]; then 542 | touch ${LDIR}/nightscout-treatments.log 543 | if ! cat ${LDIR}/nightscout-treatments.log | egrep "$createdAt"; then 544 | sensorSerialCode=$(jq ".[$index].notes" $file) 545 | sensorSerialCode="${sensorSerialCode%\"}" 546 | sensorSerialCode="${sensorSerialCode#\"}" 547 | 548 | start_date=$(date "+%s%3N" -d "$createdAt") 549 | log "Processing sensor start retrieved from Nightscout - startdate = $createdAt, sensorCode = $sensorSerialCode" 550 | # comment out below line for testing sensor start without actually sending tx message 551 | # always send sensorSerialCode even if it is blank - doesn't matter for g5, but needed 552 | # for g6 553 | startJSON="[{\"date\":\"${start_date}\",\"type\":\"StartSensor\",\"sensorSerialCode\":\"${sensorSerialCode}\"}]" 554 | log "startJSON = $startJSON" 555 | # below done so that next time the egrep returns positive for this specific message and the log reads right 556 | echo "Already Processed Sensor Start Message from Nightscout at $createdAt" >> ${LDIR}/nightscout-treatments.log 557 | # Always clear LSR cache for any new firmware g6 start / stop 558 | if [ "$(newFirmware $tx_version)" == "true" ]; then 559 | log "clearing calibration due to Sensor Start and tx version $tx_version" 560 | ClearCalibrationInput 561 | ClearCalibrationCache 562 | else 563 | log "Not clearing calibration due to Sensor Start, tx version $tx_version" 564 | fi 565 | 566 | #update xdripjs.json with new sensor code 567 | if [ "$sensorSerialCode" != "null" -a "$sensorSerialCode" != "" ]; then 568 | config="/root/myopenaps/xdripjs.json" 569 | if [ -e "$config" ]; then 570 | tmp=$(mktemp) 571 | jq --arg sensorSerialCode "$sensorSerialCode" '.sensor_code = $sensorSerialCode' "$config" > "$tmp" && mv "$tmp" "$config" 572 | fi 573 | fi 574 | 575 | fi 576 | fi 577 | fi 578 | fi 579 | } 580 | 581 | # remove old calibration storage when sensor change occurs 582 | # calibrate after 15 minutes of sensor change time entered in NS 583 | function check_sensor_change() 584 | { 585 | if [ "$mode" == "read-only" ]; then 586 | return 587 | fi 588 | 589 | 590 | curl --compressed -m 30 -H "API-SECRET: ${API_SECRET}" "${NIGHTSCOUT_HOST}/api/v1/treatments.json?find\[created_at\]\[\$gte\]=$(date -d "15 minutes ago" --iso-8601=seconds $UTCString )&find\[eventType\]\[\$regex\]=Sensor.Change" 2>/dev/null | grep "Sensor Change" 591 | if [ $? == 0 ]; then 592 | log "sensor change within last 15 minutes - clearing calibration files" 593 | ClearCalibrationInput 594 | ClearCalibrationCache 595 | touch ${LDIR}/last_sensor_change 596 | state_id=0x02 597 | state="Paused" ; stateString=$state ; stateStringShort=$state 598 | post_cgm_ns_pill 599 | 600 | log "exiting" 601 | exit 602 | fi 603 | 604 | curl --compressed -m 30 -H "API-SECRET: ${API_SECRET}" "${NIGHTSCOUT_HOST}/api/v1/treatments.json?find\[created_at\]\[\$gte\]=$(date -d "15 minutes ago" --iso-8601=seconds $UTCString)&find\[eventType\]\[\$regex\]=Sensor.Stop" 2>/dev/null | grep "Sensor Stop" 605 | if [ $? == 0 ]; then 606 | log "sensor stopped within last 15 minutes - clearing calibration files" 607 | ClearCalibrationInput 608 | ClearCalibrationCache 609 | touch ${LDIR}/last_sensor_change 610 | state_id=0x02 611 | state="Paused" ; stateString=$state ; stateStringShort=$state 612 | post_cgm_ns_pill 613 | 614 | log "exiting" 615 | exit 616 | fi 617 | 618 | curl --compressed -m 30 -H "API-SECRET: ${API_SECRET}" "${NIGHTSCOUT_HOST}/api/v1/treatments.json?find\[created_at\]\[\$gte\]=$(date -d "15 minutes ago" --iso-8601=seconds $UTCString)&find\[eventType\]\[\$regex\]=Sensor.Start" 2>/dev/null | grep "Sensor Start" 619 | if [ $? == 0 ]; then 620 | log "sensor start within last 15 minutes - clearing calibration files" 621 | ClearCalibrationInput 622 | ClearCalibrationCache 623 | touch ${LDIR}/last_sensor_change 624 | state_id=0x02 625 | state="Starting" ; stateString=$state ; stateStringShort=$state 626 | post_cgm_ns_pill 627 | 628 | log "exiting" 629 | exit 630 | fi 631 | } 632 | 633 | function check_last_entry_values() 634 | { 635 | # RESOLVED: check file stamp for > x for last-entry.json and ignore lastGlucose if older than x minutes 636 | # if within last 11 minutes 637 | if test `find $lastEntryFile -mmin -11` 638 | then 639 | if [ -e "$lastEntryFile" ] ; then 640 | lastGlucose=$(cat $lastEntryFile | jq -M '.[0].sgv') 641 | lastGlucoseDate=$(cat $lastEntryFile | jq -M '.[0].date') 642 | lastStatus=$(cat $lastEntryFile | jq -M '.[0].status') 643 | lastStatus="${lastStatus%\"}" 644 | lastStatus="${lastStatus#\"}" 645 | lastFiltered=$(cat $lastEntryFile | jq -M '.[0].filtered') 646 | lastUnfiltered=$(cat $lastEntryFile | jq -M '.[0].unfiltered') 647 | if [ "$mode" != "expired" ]; then 648 | lastState=$(cat $lastEntryFile | jq -M '.[0].state') 649 | lastState="${lastState%\"}" 650 | lastState="${lastState#\"}" 651 | fi 652 | log "check_last_entry_values: lastGlucose=$lastGlucose, lastStatus=$lastStatus, lastState=$lastState" 653 | fi 654 | fi 655 | } 656 | 657 | function updateCalibrationCache() 658 | { 659 | local filtered=$1 660 | local unfiltered=$2 661 | local meterbg=$3 662 | local meterbgid=$4 663 | local datetime=$5 # string form of date/time 664 | local epochdate=$6 # epoch date in seconds 665 | local enteredBy=$7 666 | 667 | log "updateCalibrationCache, filtered=$filtered, unfiltered=$unfiltered, meterbg=$meterbg" 668 | log " meterbgid=$meterbgid, datetime=$datetime" 669 | log " epochdate=$epochdate, enteredBy=$enteredBy" 670 | 671 | local variation=0 672 | local after=$epochdate 673 | local before=$epochdate 674 | local f=$calCacheFile 675 | 676 | if [ $(bc <<< "$after > 1") -eq 1 ]; then 677 | after=$(($epochdate+1)) 678 | fi 679 | 680 | if [ $(bc <<< "$before > 1") -eq 1 ]; then 681 | before=$(($epochdate-1)) 682 | fi 683 | 684 | 685 | # grep txepochdate in to see if this tx calibration is known yet or not 686 | # The tx reports a time in ms that shifts each and every time, so to be sure 687 | # to not have duplicates, grep for the second before and second after 688 | if cat $f | egrep "$epochdate" || cat $f | egrep "$after" || cat $f | egrep "$before"; then 689 | log "Already processed calibration of $meterbg with id = $epochdate" 690 | return 691 | fi 692 | 693 | if cat $f | egrep "$meterbgid"; then 694 | log "Already processed calibration of $meterbg with id = $meterbgid" 695 | return 696 | fi 697 | 698 | 699 | if [ "$(validBG $unfiltered)" == "false" ]; then 700 | log "Calibration of $meterbg not being used due to unfiltered of $unfiltered" 701 | return 702 | fi 703 | 704 | # safety check to make sure we don't have wide variance between the meterbg and the unfiltered value 705 | # Use 1 as slope for safety in this check 706 | meterbg_delta=$(bc -l <<< "$meterbg - $unfiltered/1") 707 | # calculate absolute value 708 | if [ $(bc -l <<< "$meterbg_delta < 0") -eq 1 ]; then 709 | meterbg_delta=$(bc -l <<< "0 - $meterbg_delta") 710 | fi 711 | if [ $(bc -l <<< "$meterbg_delta > 150") -eq 1 ]; then 712 | log "Raw/unfiltered compared to meterbg is $meterbg_delta > 150, ignoring calibration" 713 | return 714 | fi 715 | 716 | variation=$(calc_variation $filtered $unfiltered) 717 | if [ $(bc <<< "$variation > 10") -eq 1 ]; then 718 | log "would not allow calibration - filtered/unfiltered variation of $variation exceeds 10%" 719 | return 720 | fi 721 | 722 | log "Calibration is new and within bounds - adding to calibrations.csv" 723 | log "meterbg=$meterbg,datetime=$datetime,epochdate=$epochdate,meterbgid=$meterbgid,filtered=$filtered,unfiltered=$unfiltered" 724 | echo "$unfiltered,$meterbg,$datetime,$epochdate,$meterbgid,$filtered,$unfiltered,$enteredBy" >> $calCacheFile 725 | /usr/local/bin/cgm-calc-calibration $calCacheFile $calibrationFile 726 | } 727 | 728 | function seen_before() 729 | { 730 | # pass unique id as arg1 and it will return "No" if not seen before 731 | # Optional arg2 that is the application usage for unique id check 732 | # or "Yes" if it is the first and only time this id has been seen 733 | # Remembers up to 200 last unique id 734 | bg=${1:-"null"} # arg 1 is meter bg value 735 | 736 | 737 | local uid=$1 738 | local app=${2:-"Logger"} 739 | local processed_before="No" 740 | local f="${LDIR}/already_processed.txt" 741 | local t=$(mktemp) 742 | 743 | if [ -e $f ]; then 744 | if cat $f | egrep "$uid"; then 745 | processed_before="Yes" 746 | fi 747 | else 748 | touch $f 749 | fi 750 | 751 | if [[ "$processed_before" == "No" ]]; then 752 | echo "$datetime, seen at least once, $uid, $app" >> $f 753 | fi 754 | 755 | echo $processed_before 756 | 757 | # keep only last 200 seen unique ids 758 | cp $f $t 759 | tail -200 $t > $f 760 | } 761 | 762 | function check_tx_calibration() 763 | { 764 | if [ "$mode" == "read-only" ]; then 765 | return 766 | fi 767 | 768 | # TODO: remove - it is likely not necessary anymore 769 | if [[ "$sentLoggerCalibrationToTx" == true ]]; then 770 | # This is the reflection of the cmd line based calibration. 771 | # Do not process it twice 772 | return 773 | fi 774 | 775 | TXCALFILE="${LDIR}/tx-calibration-data.json" 776 | 777 | if [ -e $TXCALFILE ]; then 778 | 779 | txdatetime=$(jq ".date" $TXCALFILE) 780 | txdatetime="${txdatetime%\"}" 781 | txdatetime="${txdatetime#\"}" 782 | txmeterbg=$(jq ".glucose" $TXCALFILE) 783 | txepochdate=`date --date="$txdatetime" +"%s"` 784 | txmeterbgid=$txepochdate 785 | # calibrations.csv "unfiltered,meterbg,datetime,epochdate,meterbgid,filtered,unfiltered" 786 | seen=$(seen_before $txepochdate "Calibration from Tx") 787 | if [[ "$seen_before" == "No" ]]; then 788 | log "Tx last calibration of $txmeterbg being considered - id = $txmeterbgid, txdatetime= $txdatetime" 789 | else 790 | log "Tx last calibration of $txmeterbg seen before, already processed - id = $txmeterbgid, txdatetime= $txdatetime" 791 | return 792 | fi 793 | 794 | epochdateNow=$(date +'%s') 795 | 796 | if [ $(bc <<< "($epochdateNow - $txepochdate) < 420") -eq 1 ]; then 797 | log "tx meterbg is within 7 minutes so use current filtered/unfiltered values " 798 | txfiltered=$filtered 799 | txunfiltered=$unfiltered 800 | else 801 | log "tx meterbg is older than 7 minutes so queryfiltered/unfiltered values" 802 | # after=$(date -d "15 minutes ago" -Iminutes) 803 | # glucosejqstr="'[ .[] | select(.dateString > \"$after\") ]'" 804 | before=$(bc -l <<< "$txepochdate *1000 + 240*1000") 805 | after=$(bc -l <<< "$txepochdate *1000 - 240*1000") 806 | glucosejqstr="'[ .[] | select(.date > $after) | select(.date < $before) ]'" 807 | 808 | bash -c "jq -c $glucosejqstr ~/myopenaps/monitor/glucose.json" > ${LDIR}/test.json 809 | txunfiltered=( $(jq -r ".[0].unfiltered" ${LDIR}/test.json) ) 810 | txfiltered=( $(jq -r ".[0].filtered" ${LDIR}/test.json) ) 811 | fi 812 | 813 | updateCalibrationCache $txfiltered $txunfiltered $txmeterbg $txmeterbgid "$txdatetime" $txepochdate "Logger-tx" 814 | # use enteredBy Logger so that 815 | # it can be filtered and not reprocessed by Logger again 816 | readyCalibrationToNS $txdatetime $txmeterbg "Logger-tx" 817 | postTreatmentsToNS 818 | rm -f $TXCALFILE 819 | fi 820 | } 821 | 822 | 823 | function addToMessages() 824 | { 825 | local jsonToAdd=$1 826 | local resultJSON="" 827 | local msgFile=$2 828 | 829 | log "addToMessages jsonToAdd=$jsonToAdd" 830 | 831 | jqType=$(jq type <<< "$jsonToAdd") 832 | if [[ "$jqType" != *"array"* ]]; then 833 | log "jsonToAdd is not valid = $jsonToAdd" 834 | return 835 | fi 836 | 837 | local lengthJSON=${#jsonToAdd} 838 | 839 | if [ $(bc <<< "$lengthJSON < 2") -eq 1 ]; then 840 | log "jsonToAdd is not valid too short = $jsonToAdd" 841 | return 842 | fi 843 | 844 | if [ -e $msgFile ]; then 845 | local stagingFile1=$(mktemp) 846 | local stagingFile2=$(mktemp) 847 | echo "$jsonToAdd" > $stagingFile1 848 | cp $msgFile $stagingFile2 849 | log "stagingFile2 is below" 850 | cat $stagingFile2 851 | log "stagingFile1 is below" 852 | cat $stagingFile1 853 | resultJSON=$(jq -c -s add $stagingFile2 $stagingFile1) 854 | 855 | else 856 | resultJSON=$jsonToAdd 857 | fi 858 | 859 | jqType=$(jq type <<< "$resultJSON") 860 | if [[ "$jqType" != *"array"* ]]; then 861 | log "resultJSON is not valid = $resultJSON" 862 | return 863 | fi 864 | 865 | log "resultJSON=$resultJSON" 866 | echo "$resultJSON" > $msgFile 867 | } 868 | 869 | # TODO: make one of these for start/stop treatments 870 | 871 | function readyCalibrationToNS() 872 | { 873 | # takes a calibration record and puts it in json file for later sending it to NS 874 | local createDate="$1" 875 | local meterbg=$2 876 | local enteredBy="$3" 877 | 878 | 879 | # arg1 = createDate in string format T ... Z 880 | # arg2 = meterbg 881 | # arg3 = enteredBy 882 | 883 | calibrationNSFile=$(mktemp) 884 | stagingFile=$(mktemp) 885 | 886 | 887 | log "Setting up to send calibration to NS now if online (or later with backfill)" 888 | echo "[{\"created_at\":\"$createDate\",\"enteredBy\":\"$enteredBy\",\"reason\":\"sensor calibration\",\"eventType\":\"BG Check\",\"glucose\":$meterbg,\"glucoseType\":\"Finger\",\"units\":\"mg/dl\"}]" > $calibrationNSFile 889 | cat $calibrationNSFile 890 | 891 | if [ -e $treatmentsFile ]; then 892 | cp $treatmentsFile $stagingFile 893 | jq -s add $calibrationNSFile $stagingFile > $treatmentsFile 894 | else 895 | cp $calibrationNSFile $treatmentsFile 896 | fi 897 | } 898 | 899 | function generate_uuid() 900 | { 901 | cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1 902 | } 903 | 904 | function check_cmd_line_calibration() 905 | { 906 | if [ "$mode" == "read-only" ]; then 907 | return 908 | fi 909 | ## look for a bg check from ${LDIR}/calibration.json 910 | if [ -e $CALFILE ]; then 911 | epochdatems=$(date +'%s%3N') 912 | if test `find $CALFILE -mmin -7` 913 | then 914 | log "calibration file $CALFILE contents below" 915 | cat $CALFILE 916 | echo 917 | 918 | cJSON=$(cat $CALFILE) 919 | addToMessages "$cJSON" $calibrationMessageFile 920 | 921 | calDateA=( $(jq -r ".[].date" ${CALFILE}) ) 922 | meterbgA=( $(jq -r ".[].glucose" ${CALFILE}) ) 923 | calRecords=${#meterbgA[@]} 924 | log "Calibration records from command line=$calRecords" 925 | 926 | # Leverage tx reflection here. The first calibration is the latest one 927 | # based on how the command line utility does it 928 | # so process the records in reverse order 929 | # Consider in the future: use lastFiltered and lastUnfiltered 930 | # values if they exist, otherwise 931 | # do LSR here and set variable so that 932 | # reflective Tx calibration doesn't duplicate 933 | for (( i=$calRecords-1; i>=0; i-- )) 934 | do 935 | calDate=${calDateA[$i]} 936 | # check the date inside to make sure we don't calibrate using old record 937 | if [ $(bc <<< "($epochdatems - $calDate)/1000 < 820") -eq 1 ]; then 938 | calDateSeconds=$(bc <<< "($calDate / 1000)") # truncate 939 | meterbg=${meterbgA[$i]} 940 | meterbgid=$(generate_uuid) 941 | log "Calibration of $meterbg from $CALFILE being processed - id = $meterbgid" 942 | found_meterbg=true 943 | # put in backfill so that the command line calibration will be sent up to NS 944 | # now (or later if offline) 945 | 946 | createdAt=$(date $UTCString -d @$calDateSeconds +'%Y-%m-%dT%H:%M:%S.%3NZ') 947 | 948 | if [ $(bc <<< "$lastUnfiltered > 0") -eq 1 ]; then 949 | updateCalibrationCache $lastFiltered $lastUnfiltered $meterbg $meterbgid "$createdAt" $calDateSeconds "Logger-cmd-line" 950 | sentLoggerCalibrationToTx=true 951 | fi 952 | readyCalibrationToNS $createdAt $meterbg "Logger-cmd-line" 953 | postTreatmentsToNS 954 | else 955 | log "Calibration is too old - not used" 956 | fi 957 | done 958 | fi 959 | rm -f $CALFILE 960 | log "meterbg from ${LDIR}/calibration.json: $meterbg" 961 | fi 962 | } 963 | 964 | function remove_dexcom_bt_pair() 965 | { 966 | log "Removing existing Dexcom bluetooth connection = ${id}" 967 | bt-device -r $id 2> /dev/null 968 | 969 | # Also remove the mac address tx pairing if exists 970 | sfile="${LDIR}/saw-transmitter.json" 971 | 972 | if [ -e $sfile ]; then 973 | mac=$(cat $sfile | jq -M '.address') 974 | mac="${mac%\"}" 975 | mac="${mac#\"}" 976 | if [ ${#mac} -ge 8 ]; then 977 | smac=${mac//:/-} 978 | smac=${smac^^} 979 | #echo $smac 980 | log "Removing existing Dexcom bluetooth mac connection also = ${smac}" 981 | bt-device -r $smac 2> /dev/null 982 | fi 983 | fi 984 | } 985 | 986 | function initialize_messages() 987 | { 988 | stopJSON="" 989 | startJSON="" 990 | batteryJSON="" 991 | resetJSON="" 992 | versionJSON="" 993 | } 994 | 995 | function compile_messages() 996 | { 997 | if [ "${resetJSON}" != "" ]; then 998 | addToMessages "$resetJSON" $xdripMessageFile 999 | fi 1000 | 1001 | if [ "${versionJSON}" != "" ]; then 1002 | addToMessages "$versionJSON" $xdripMessageFile 1003 | fi 1004 | 1005 | if [ "${stopJSON}" != "" ]; then 1006 | addToMessages "$stopJSON" $xdripMessageFile 1007 | fi 1008 | 1009 | if [ "${startJSON}" != "" ]; then 1010 | addToMessages "$startJSON" $xdripMessageFile 1011 | fi 1012 | 1013 | if [ "${batteryJSON}" != "" ]; then 1014 | addToMessages "$batteryJSON" $xdripMessageFile 1015 | fi 1016 | 1017 | if [ -e $calibrationMessageFile ]; then 1018 | local calibrationJSON=$(cat $calibrationMessageFile) 1019 | 1020 | addToMessages "$calibrationJSON" $xdripMessageFile 1021 | rm -f $calibrationMessageFile 1022 | fi 1023 | 1024 | 1025 | messages="" 1026 | if [ -e $xdripMessageFile ]; then 1027 | messages=$(cat $xdripMessageFile) 1028 | log "messages=$messages" 1029 | fi 1030 | 1031 | if [ "$messages" == "" ]; then 1032 | echo "[]" > $xdripMessageFile 1033 | log "clearing out logger to xdrip-js messages" 1034 | fi 1035 | 1036 | } 1037 | 1038 | 1039 | function call_logger() 1040 | { 1041 | log "Calling xdrip-js ... node logger $transmitter $xdripMessageFile $alternateBluetoothChannel" 1042 | DEBUG=smp,transmitter,bluetooth-manager,backfill-parser 1043 | export DEBUG 1044 | # echo -n "Timezone = " 1045 | # env | grep TZ 1046 | 1047 | timeout 420 node logger $transmitter $xdripMessageFile $alternateBluetoothChannel 1048 | echo 1049 | local error="" 1050 | log "after xdrip-js bg record below ..." 1051 | if [ -e "${LDIR}/entry.json" ]; then 1052 | cat ${LDIR}/entry.json 1053 | touch ${LDIR}/entry-watchdog 1054 | echo 1055 | glucose=$(cat ${LDIR}/entry.json | jq -M '.[0].glucose') 1056 | unfiltered=$(cat ${LDIR}/entry.json | jq -M '.[0].unfiltered') 1057 | unfiltered=$(bc -l <<< "scale=0; $unfiltered / 1000") 1058 | if [ "$(validBG $unfiltered)" == "false" -a "$(validBG $glucose)" == "false" ]; then 1059 | error="Invalid response - Unf=$unfiltered, gluc=$glucose" 1060 | state_id=0x25 1061 | ls -al ${LDIR}/entry.json 1062 | cat ${LDIR}/entry.json 1063 | rm ${LDIR}/entry.json 1064 | fi 1065 | # remove start/stop message files only if not rebooting and we acted on them 1066 | if [[ -n "$stopJSON" ]]; then 1067 | rm -f $cgm_stop_file 1068 | fi 1069 | if [[ -n "$startJSON" ]]; then 1070 | rm -f $cgm_start_file 1071 | fi 1072 | else 1073 | state_id=0x24 1074 | error="No Response" 1075 | bt_watchdog 1076 | fi 1077 | if [ "$error" != "" ]; then 1078 | state=$error ; stateString=$state ; stateStringShort=$state 1079 | remove_dexcom_bt_pair 1080 | post_cgm_ns_pill 1081 | exit 1082 | fi 1083 | } 1084 | 1085 | # reads the json file that stores the latest known tx firmware version 1086 | function getTxVersion() 1087 | { 1088 | if [ -e "${LDIR}/tx-version.json" ]; then 1089 | tx_version=$(txVersion) 1090 | if [ "$(newFirmware $tx_version)" == "true" ]; then 1091 | log "Dexcom tx version is $tx_version (new firmware)" 1092 | else 1093 | log "Dexcom tx version is $tx_version (not new firmware)" 1094 | fi 1095 | fi 1096 | } 1097 | 1098 | function capture_entry_values() 1099 | { 1100 | # capture values for use and for log to csv file 1101 | unfiltered=$(cat ${LDIR}/entry.json | jq -M '.[0].unfiltered') 1102 | filtered=$(cat ${LDIR}/entry.json | jq -M '.[0].filtered') 1103 | 1104 | # convert data to scale of 1 vs 1000x 1105 | unfiltered=$(bc -l <<< "scale=0; $unfiltered / 1000") 1106 | filtered=$(bc -l <<< "scale=0; $filtered / 1000") 1107 | 1108 | state=$(cat ${LDIR}/entry.json | jq -M '.[0].state') 1109 | state="${state%\"}" 1110 | state="${state#\"}" 1111 | 1112 | state_id=$(cat ${LDIR}/extra.json | jq -M '.[0].state_id') 1113 | status_id=$(cat ${LDIR}/extra.json | jq -M '.[0].status_id') 1114 | transmitterStartDate=$(cat ${LDIR}/extra.json | jq -M '.[0].transmitterStartDate') 1115 | 1116 | # reads the json file that stores the latest known tx firmware version 1117 | getTxVersion 1118 | 1119 | transmitterStartDate="${transmitterStartDate%\"}" 1120 | transmitterStartDate="${transmitterStartDate#\"}" 1121 | log "transmitterStartDate=$transmitterStartDate" 1122 | 1123 | sessionStartDate=$(cat ${LDIR}/extra.json | jq -M '.[0].sessionStartDate') 1124 | sessionStartDate="${sessionStartDate%\"}" 1125 | sessionStartDate="${sessionStartDate#\"}" 1126 | sessionStartDateEpochms=$(cat ${LDIR}/extra.json | jq -M '.[0].sessionStartDateEpoch') 1127 | # make sure to use 7 days for g5 and 10 for g6 1128 | local sessionMaxSeconds=$SECONDS_IN_10_DAYS 1129 | if [ "$txType" == "g5" ]; then 1130 | sessionMaxSeconds=$SECONDS_IN_7_DAYS 1131 | fi 1132 | 1133 | # check for valid and not expired sessionStartDate 1134 | sessionMinutesRemaining=$(bc <<< "($sessionMaxSeconds - ($epochdate-$sessionStartDateEpochms/1000))/60") 1135 | if [ $(bc <<< "$sessionMinutesRemaining < 0") -eq 1 -a $(bc <<< "$sessionMinutesRemaining > ($sessionMaxSeconds * 60)") -eq 1 ]; then 1136 | log "Expired session or invalid sessionStartDate, not processing auto-restart logic" 1137 | fi 1138 | log "sessionStartDate=$sessionStartDate, sessionStartDateEpochms=$sessionStartDateEpochms" 1139 | log "sessionMinutesRemaining=$sessionMinutesRemaining" 1140 | if [ $(bc <<< "$sessionMinutesRemaining < 65") -eq 1 ]; then 1141 | if [ $(bc <<< "$glucose < 400") -eq 1 -a $(bc <<< "$glucose > 40") -eq 1 ]; then 1142 | if [ $(bc <<< "$variation < 10") -eq 1 ]; then 1143 | if [[ "$auto_sensor_restart" == true ]]; then 1144 | cgm-stop; sleep 2; cgm-start -m 120; sleep 2; cgm-calibrate $glucose 1145 | touch ${LDIR}/lsr-calibrates-native-next-cycle 1146 | # Touch the file 18 hours in the future in order to 1147 | # disable native-calibrates LSR for exactly 24 hours from now 1148 | touch -d "-18 hours ago" ${LDIR}/native-calibrates-lsr 1149 | fi 1150 | fi 1151 | fi 1152 | fi 1153 | 1154 | rssi=$(cat ${LDIR}/entry.json | jq -M '.[0].rssi') 1155 | 1156 | status=$(cat ${LDIR}/entry.json | jq -M '.[0].status') 1157 | status="${status%\"}" 1158 | status="${status#\"}" 1159 | log "Sensor state = $state" 1160 | log "Transmitter status = $status" 1161 | 1162 | orig_status=$status 1163 | orig_state=$state 1164 | orig_status_id=$status_id 1165 | orig_state_id=$state_id 1166 | 1167 | # get dates for use in filenames and json entries 1168 | datetime=$(date +"%Y-%m-%d %H:%M") 1169 | epochdate=$(date +'%s') 1170 | cp -p ${LDIR}/entry.json $lastEntryFile 1171 | } 1172 | 1173 | function process_if_lsr_calibrates_native() 1174 | { 1175 | local file="${LDIR}/lsr-calibrates-native-next-cycle" 1176 | if [ -e $file ]; then 1177 | if [[ "$auto_sensor_restart" == true ]]; then 1178 | if [ $(bc <<< "$variation < 10") -eq 1 ]; then 1179 | # send calibrate to be processed next cycle to tx based on LSR 1180 | if [ "$(validBG $calibratedBG)" == "true" ]; then 1181 | rm $file 1182 | # check to make sure we have more than 2 LSR records 1183 | if [ $(bc <<< "$calRecords > 2") -eq 1 ]; then 1184 | cgm-calibrate $calibratedBG 1185 | fi 1186 | fi 1187 | fi 1188 | fi 1189 | fi 1190 | } 1191 | 1192 | function checkif_fallback_mode() 1193 | { 1194 | fallback=false 1195 | if [ "$mode" == "read-only" ]; then 1196 | return 1197 | fi 1198 | 1199 | if [ "$mode" != "expired" ]; then 1200 | # did we not get a valid internal tx calibrated glucose 1201 | if [ "$(validBG $glucose)" == "false" ]; then 1202 | # fallback to try to use unfiltered in this case 1203 | mode="expired" 1204 | fallback=true 1205 | log "Due to tx calibrated glucose of $glucose, Logger will temporarily fallback to mode=$mode" 1206 | fi 1207 | fi 1208 | } 1209 | 1210 | function initialize_mode() 1211 | { 1212 | # This is the default so that calibrations from tx generated BG values 1213 | # to reflect in LSR in order to make it safer to allow seamless transition to LSR calibration 1214 | mode="native-calibrates-lsr" 1215 | 1216 | if [[ "$cmd_line_mode" == "expired" ]]; then 1217 | mode="expired" 1218 | fi 1219 | 1220 | if [[ "$cmd_line_mode" == "not-expired" ]]; then 1221 | mode="not-expired" 1222 | fi 1223 | 1224 | 1225 | if [[ "$cmd_line_mode" == "read-only" ]]; then 1226 | mode="read-only" 1227 | fi 1228 | log "Logger mode=$mode" 1229 | } 1230 | 1231 | function initialize_calibrate_bg() 1232 | { 1233 | calibratedBG=$glucose 1234 | tmp=$(mktemp) 1235 | jq ".[0].sgv = $glucose" ${LDIR}/entry.json > "$tmp" && mv "$tmp" ${LDIR}/entry.json 1236 | } 1237 | 1238 | function set_entry_fields() 1239 | { 1240 | tmp=$(mktemp) 1241 | jq ".[0].device = \"${transmitter}\"" ${LDIR}/entry.json > "$tmp" && mv "$tmp" ${LDIR}/entry.json 1242 | tmp=$(mktemp) 1243 | jq ".[0].filtered = ${filtered}" ${LDIR}/entry.json > "$tmp" && mv "$tmp" ${LDIR}/entry.json 1244 | tmp=$(mktemp) 1245 | jq ".[0].unfiltered = ${unfiltered}" ${LDIR}/entry.json > "$tmp" && mv "$tmp" ${LDIR}/entry.json 1246 | } 1247 | 1248 | 1249 | function log_cgm_csv() 1250 | { 1251 | file="/var/log/openaps/cgm.csv" 1252 | noise_percentage=$(bc <<< "$noise * 100") 1253 | 1254 | noiseToLog=${noise} 1255 | if [ "$noiseToLog" == "null" -o "$noiseToLog" == "" ]; then 1256 | noiseToLog="Other" 1257 | fi 1258 | 1259 | if [ ! -f $file ]; then 1260 | echo "epochdate,datetime,unfiltered,filtered,direction,calibratedBG-lsr,cgm-glucose,meterbg,slope,yIntercept,slopeError,yError,rSquared,Noise,NoiseSend,mode,noise*100,sensitivity,rssi" > $file 1261 | fi 1262 | echo "${epochdate},${datetime},${unfiltered},${filtered},${direction},${calibratedBG},${glucose},${meterbg},${slope},${yIntercept},${slopeError},${yError},${rSquared},${noiseToLog},${noiseSend},${mode},${noise_percentage},${sensitivity},${rssi}" >> $file 1263 | } 1264 | 1265 | 1266 | function postAnnouncementToNS() 1267 | { 1268 | local announcement=$1 1269 | 1270 | echo "[{\"enteredBy\":\"Logger\",\"eventType\":\"Announcement\",\"notes\":\"$announcement\"}]" > ${LDIR}/status-change.json 1271 | /usr/local/bin/cgm-post-ns ${LDIR}/status-change.json treatments && (echo; log "Upload to NightScout of Announcment worked - $announcement") || (echo; log "Upload of Announcement to NS did not work - $announcement") 1272 | } 1273 | 1274 | # if tx state or status changed, then post a note to NS 1275 | function process_announcements() 1276 | { 1277 | if [ "$state" == "Stopped" ] && [ "$mode" != "expired" ]; then 1278 | log "Not posting glucose to Nightscout or OpenAPS - sensor state is Stopped, unfiltered=$unfiltered" 1279 | postAnnouncementToNS "Sensor Stopped, unfiltered=$unfiltered" 1280 | else 1281 | log "process_announcements: state=$state lastState=$lastState status=$status lastStatus=$lastStatus" 1282 | if [ "$status" != "$lastStatus" ]; then 1283 | postAnnouncementToNS "Tx $status $postAnnouncementToNSAdder" 1284 | fi 1285 | 1286 | if [ "$state" != "$lastState" ]; then 1287 | postAnnouncementToNS "Sensor $state" 1288 | fi 1289 | fi 1290 | } 1291 | 1292 | function check_last_calibration() 1293 | { 1294 | if [ "$mode" == "read-only" ]; then 1295 | return 1296 | fi 1297 | 1298 | if $fallback ; then 1299 | state=$orig_state 1300 | status=$orig_status 1301 | state_id=$orig_state_id 1302 | status_id=$orig_status_id 1303 | return 1304 | fi 1305 | 1306 | if [ "$mode" == "expired" ]; then 1307 | state="Needs Calibration" ; stateString=$state ; stateStringShort=$state 1308 | state_id=0x07 1309 | if [ -e $calibrationFile ]; then 1310 | if test `find $calibrationFile -mmin +720` 1311 | then 1312 | log "Last calibration > 12 hours ago, setting sensor state to Needs Calibration" 1313 | : # default of Needs calibration here since last one > 12 hours ago 1314 | else 1315 | log "Last calibration within 12 hours, setting sensor state to OK" 1316 | state="OK" ; stateString=$state ; stateStringShort=$state 1317 | state_id=0x06 1318 | fi 1319 | fi 1320 | fi 1321 | } 1322 | 1323 | function check_native_calibrates_lsr() 1324 | { 1325 | local file="${LDIR}/native-calibrates-lsr" 1326 | local native_calibrates_lsr_check="No" 1327 | 1328 | if [ "$mode" == "native-calibrates-lsr" ]; then 1329 | if [ -e $file ]; then 1330 | if test `find $file -mmin +360` 1331 | then 1332 | native_calibrates_lsr_check="Yes" 1333 | fi 1334 | else 1335 | native_calibrates_lsr_check="Yes" 1336 | fi 1337 | 1338 | # every 6 hours, calibrate LSR algo. via native dexcom glucose value: 1339 | # (note: _does not_ calibrate tx, since this is only called after the tx comm is over.) 1340 | if [ $(bc <<< "$glucose < 400") -eq 1 -a $(bc <<< "$glucose > 40") -eq 1 ]; then 1341 | if [ $(bc <<< "$variation < 10") -eq 1 ]; then 1342 | if [ "$native_calibrates_lsr_check" == "Yes" ]; then 1343 | meterbg=$glucose 1344 | meterbgid=$(generate_uuid) 1345 | calDate=$(date +'%s%3N') 1346 | calDateSeconds=$(date +'%s') 1347 | log "meterbg from native-calibrates-lsr: $meterbg" 1348 | # datetime has spaces in it and must have quotes around it 1349 | updateCalibrationCache $filtered $unfiltered $meterbg $meterbgid "$datetime" $calDateSeconds "Logger-native-calibrates-lsr" 1350 | postAnnouncementToNS "native-calibrated-lsr $meterbg" 1351 | touch $file 1352 | sentLoggerCalibrationToTx=true 1353 | found_meterbg=true 1354 | fi 1355 | fi 1356 | fi 1357 | fi 1358 | } 1359 | 1360 | # Pump History BG records 1361 | # { 1362 | # "timestamp": "2017-06-07T06:30:09-04:00", 1363 | # "_type": "CalBGForPH", 1364 | # "id": "Ck9JniZnEQ==", 1365 | # "amount": 79, 1366 | # "units": "mgdl" 1367 | # } 1368 | # { 1369 | # "timestamp": "2017-06-07T06:30:09-04:00", 1370 | # "_type": "BGReceived", 1371 | # "id": "PwlJnuZnEcI2Rw==", 1372 | # "link": "C23647", 1373 | # "amount": 79, 1374 | # "units": "mgdl" 1375 | # } 1376 | function check_pump_history_calibration() 1377 | { 1378 | if [ "$mode" == "read-only" ]; then 1379 | return 1380 | fi 1381 | 1382 | if [[ "$found_meterbg" == false ]]; then 1383 | historyFile="$HOME/myopenaps/monitor/pumphistory-24h-zoned.json" 1384 | if [ ! -e "$historyFile" ]; then 1385 | # support the old file name in case of older version of OpenAPS 1386 | historyFile="$HOME/myopenaps/monitor/pumphistory-zoned.json" 1387 | fi 1388 | 1389 | if [ -e "$historyFile" ]; then 1390 | # look for a bg check from pumphistory (direct from meter->openaps): 1391 | # note: pumphistory may not be loaded by openaps very timely... 1392 | meterbgafter=$(date -d "20 minutes ago" -Iminutes) 1393 | meterjqstr="'.[] | select(._type == \"BGReceived\") | select(.timestamp > \"$meterbgafter\")'" 1394 | bash -c "jq $meterjqstr $historyFile" > $METERBG_NS_RAW 1395 | meterbg=$(bash -c "jq .amount $METERBG_NS_RAW | head -1") 1396 | meterbgid=$(bash -c "jq .timestamp $METERBG_NS_RAW | head -1") 1397 | meterbgid="${meterbgid%\"}" 1398 | meterbgid="${meterbgid#\"}" 1399 | # meter BG from pumphistory doesn't support mmol yet - has no units... 1400 | # using arg3 if mmol then convert it 1401 | if [[ "$pumpUnits" == *"mmol"* ]]; then 1402 | meterbg=$(bc <<< "($meterbg *18)/1") 1403 | log "converted pump history meterbg from mmol value to $meterbg" 1404 | fi 1405 | echo 1406 | calDate=$(date --date="$meterbgid" +"%s%3N") 1407 | if [[ -n "$meterbg" && "$meterbg" != "" ]]; then 1408 | log "meterbg from pumphistory: $meterbg" 1409 | found_meterbg=true 1410 | addToMessages "[{\"date\": ${calDate}, \"type\": \"CalibrateSensor\",\"glucose\": $meterbg}]" $calibrationMessageFile 1411 | fi 1412 | # no need to send to NS because OpenAPS does this for us 1413 | fi 1414 | fi 1415 | } 1416 | 1417 | function calc_variation() 1418 | { 1419 | # arg1 = filtered 1420 | # arg2 = unfiltered 1421 | 1422 | variationLocal=0 1423 | # check to see if filtered and unfiltered are valid numbers 1424 | # so that the new g6 firmware will still work with a valid glucose 1425 | # and null raw numbers. This means the sensor is in session and will 1426 | # handle noise on its own so zero for variation is fine in this case 1427 | if [ "$(validNumber $1)" == "true" -a "$(validNumber $2)" == "true" ]; then 1428 | if [ $(bc <<< "$1 > 0") -eq 1 ]; then 1429 | variationLocal=$(bc <<< "($1 - $2) * 100 / $1") 1430 | if [ $(bc <<< "$variationLocal < 0") -eq 1 ]; then 1431 | variationLocal=$(bc <<< "0 - $variationLocal") 1432 | fi 1433 | fi 1434 | fi 1435 | 1436 | echo $variationLocal 1437 | } 1438 | 1439 | function check_variation() 1440 | { 1441 | if [ "$mode" == "read-only" ]; then 1442 | return 1443 | fi 1444 | 1445 | # always calc variation because its value is used elsewhere 1446 | variation=$(calc_variation $filtered $unfiltered) 1447 | 1448 | if [[ "$found_meterbg" == true ]]; then 1449 | if [ $(bc <<< "$variation > 10") -eq 1 ]; then 1450 | log "would not allow meter calibration - filtered/unfiltered variation of $variation exceeds 10%" 1451 | meterbg="" 1452 | else 1453 | log "filtered/unfiltered variation ok for meter calibration, $variation" 1454 | fi 1455 | fi 1456 | } 1457 | 1458 | function check_ns_calibration() 1459 | { 1460 | if [ "$mode" == "read-only" ]; then 1461 | return 1462 | fi 1463 | 1464 | if [[ "$found_meterbg" == false ]]; then 1465 | # can't use the Sensor insert UTC determination for BG since they can 1466 | # be entered in either UTC or local time depending on how they were entered. 1467 | curl --compressed -m 30 -H "API-SECRET: ${API_SECRET}" "${ns_url}/api/v1/treatments.json?find\[eventType\]\[\$regex\]=Check&count=1" 2>/dev/null > $METERBG_NS_RAW 1468 | createdAt=$(jq -r ".[0].created_at" $METERBG_NS_RAW) 1469 | if [ "$createdAt" == "null" ] ; then 1470 | return 1471 | fi 1472 | secNow=`date +%s` 1473 | secThen=`date +%s --date=$createdAt` 1474 | secThenMs=`date +%s%3N --date=$createdAt` 1475 | elapsed=$(bc <<< "($secNow - $secThen)") 1476 | #log "meterbg date=$createdAt, secNow=$secNow, secThen=$secThen, elapsed=$elapsed" 1477 | if [ $(bc <<< "$elapsed < 540") -eq 1 ]; then 1478 | # note: pumphistory bg has no _id field, but .timestamp matches .created_at 1479 | enteredBy=$(jq ".[0].enteredBy" $METERBG_NS_RAW) 1480 | enteredBy="${enteredBy%\"}" 1481 | enteredBy="${enteredBy#\"}" 1482 | if [[ "$enteredBy" == *"Logger"* ]]; then 1483 | # Logger knows about it already so don't process again 1484 | return 1485 | fi 1486 | 1487 | meterbgid=$(jq ".[0].created_at" $METERBG_NS_RAW) 1488 | meterbgid="${meterbgid%\"}" 1489 | meterbgid="${meterbgid#\"}" 1490 | meterbgunits=$(cat $METERBG_NS_RAW | jq -M '.[0] | .units') 1491 | meterbg=`jq -M '.[0] .glucose' $METERBG_NS_RAW` 1492 | meterbg="${meterbg%\"}" 1493 | meterbg="${meterbg#\"}" 1494 | if [[ "$meterbgunits" == *"mmol"* ]]; then 1495 | meterbg=$(bc <<< "($meterbg *18)/1") 1496 | fi 1497 | found_meterbg=true 1498 | # nothing to do here except prepare xdrip-js message 1499 | calDate=$secThenMs 1500 | addToMessages "[{\"date\": ${calDate}, \"type\": \"CalibrateSensor\",\"glucose\": $meterbg}]" $calibrationMessageFile 1501 | log "meterbg from nightscout: $meterbg, date=$calDate" 1502 | else 1503 | # clear old meterbg curl responses 1504 | rm $METERBG_NS_RAW 1505 | fi 1506 | fi 1507 | } 1508 | 1509 | #call after posting to NS OpenAPS for not-expired mode 1510 | function calculate_calibrations() 1511 | { 1512 | # Do not update LSR calibration for read only mode or for invalid unfiltered value 1513 | if [ "$mode" == "read-only" -o "$(validNumber $unfiltered)" == "false" ]; then 1514 | return 1515 | fi 1516 | 1517 | calibrationDone=0 1518 | if [ -n $meterbg ]; then 1519 | if [ "$meterbg" != "null" -a "$meterbg" != "" ]; then 1520 | if [ $(bc <<< "$meterbg < 400") -eq 1 -a $(bc <<< "$meterbg > 40") -eq 1 ]; then 1521 | updateCalibrationCache $filtered $unfiltered $meterbg $meterbgid "$datetime" $epochdate "Logger" 1522 | calibrationDone=1 1523 | maxDelta=80 1524 | if [ -e $calibrationFile ]; then 1525 | cat $calibrationFile 1526 | slope=`jq -M '.[0] .slope' $calibrationFile` 1527 | yIntercept=`jq -M '.[0] .yIntercept' $calibrationFile` 1528 | 1529 | log "Posting cal record to NightScout" 1530 | # new calibration record log it to NS 1531 | #slope_div_1000=$(bc -l <<< "scale=2; $slope / 1000") 1532 | #yIntercept_div_1000=$(bc -l <<< "scale=2; $yIntercept / 1000") 1533 | 1534 | echo "[{\"device\":\"$rig\",\"type\":\"cal\",\"date\":$epochdatems,\"dateString\":\"$dateString\", \"scale\":1,\"intercept\":$yIntercept,\"slope\":$slope}]" > ${LDIR}/cal.json 1535 | cat ${LDIR}/cal.json 1536 | /usr/local/bin/cgm-post-ns ${LDIR}/cal.json && (echo; log "Upload to NightScout of cal record entry worked";) || (echo; log "Upload to NS of cal record did not work") 1537 | fi 1538 | else 1539 | log "this calibration was previously recorded - ignoring" 1540 | fi 1541 | fi 1542 | fi 1543 | } 1544 | 1545 | function apply_lsr_calibration() 1546 | { 1547 | # Do not update LSR calibration for read only mode or for invalid unfiltered value 1548 | if [ "$mode" == "read-only" -o "$(validNumber $unfiltered)" == "false" ]; then 1549 | return 1550 | fi 1551 | 1552 | if [ -e $calibrationFile ]; then 1553 | #TODO: store calibration date in json file and read here 1554 | slope=`jq -M '.[0] .slope' $calibrationFile` 1555 | yIntercept=`jq -M '.[0] .yIntercept' $calibrationFile` 1556 | slopeError=`jq -M '.[0] .slopeError' $calibrationFile` 1557 | yError=`jq -M '.[0] .yError' $calibrationFile` 1558 | calibrationType=`jq -M '.[0] .calibrationType' $calibrationFile` 1559 | calibrationType="${calibrationType%\"}" 1560 | calibrationType="${calibrationType#\"}" 1561 | numCalibrations=`jq -M '.[0] .numCalibrations' $calibrationFile` 1562 | rSquared=`jq -M '.[0] .rSquared' $calibrationFile` 1563 | else 1564 | if [ "$mode" == "expired" ]; then 1565 | # do not exit here because g6 supports no calibration mode now 1566 | # TODO: determine if g6 and in no-calibration mode somehow and do not set state to First Calibration 1567 | log "no calibration records (mode: expired)" 1568 | state_id=0x04 1569 | state="First Calibration" ; stateString=$state ; stateStringShort=$state 1570 | #post_cgm_ns_pill 1571 | #remove_dexcom_bt_pair 1572 | #exit 1573 | else 1574 | if [ "$mode" != "expired" ]; then 1575 | # exit as there is nothing to calibrate without calibration-linear.json? 1576 | log "no calibration records (mode: $mode)" 1577 | # don't exit here because g6 supports no calibration mode now 1578 | #exit 1579 | fi 1580 | fi 1581 | fi 1582 | 1583 | if [ "$yIntercept" != "" -a "$slope" != "" ]; then 1584 | calibratedBG=$(bc -l <<< "($unfiltered - $yIntercept)/$slope") 1585 | calibratedBG=$(round $calibratedBG) # round to nearest whole number in mg/dl 1586 | # Logger does everything in mg/dl. Input in mmol are converted to mg/dl and all 1587 | # calculations / output are in mg/dl. NS can change it back to mmol based on user preferences. 1588 | log "After calibration calibratedBG =$calibratedBG, slope=$slope, yIntercept=$yIntercept, filtered=$filtered, unfiltered=$unfiltered" 1589 | else 1590 | calibratedBG=0 1591 | if [ "$mode" == "expired" ]; then 1592 | state_id=0x07 1593 | state="Needs Calibration" ; stateString=$state ; stateStringShort=$state 1594 | post_cgm_ns_pill 1595 | remove_dexcom_bt_pair 1596 | log "expired mode with no calibration - exiting" 1597 | exit 1598 | fi 1599 | fi 1600 | 1601 | # For Single Point calibration, use a calculated corrective intercept 1602 | # for glucose in the range of 70 <= BG < 85 1603 | # per https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4764224 1604 | # AdjustedBG = BG - CI 1605 | # CI = a1 * BG^2 + a2 * BG + a3 1606 | # a1=-0.1667, a2=25.66667, a3=-977.5 1607 | c=$calibratedBG 1608 | log "numCalibrations=$numCalibrations, calibrationType=$calibrationType, c=$c" 1609 | if [ "$calibrationType" = "SinglePoint" ]; then 1610 | log "inside CalibrationType=$calibrationType, c=$c" 1611 | if [ $(bc <<< "$c > 69") -eq 1 -a $(bc <<< "$c < 85") -eq 1 ]; then 1612 | log "SinglePoint calibration calculating corrective intercept" 1613 | log "Before CI, BG=$c" 1614 | calibratedBG=$(bc -l <<< "$c - (-0.16667 * ${c}^2 + 25.6667 * ${c} - 977.5)") 1615 | calibratedBG=$(bc <<< "($calibratedBG / 1)") # truncate 1616 | log "After CI, BG=$calibratedBG" 1617 | else 1618 | log "Not using CI because bg not in [70-84]" 1619 | fi 1620 | fi 1621 | 1622 | if [ -z $calibratedBG ]; then 1623 | # Outer calibrated BG boundary checks - exit and don't send these on to Nightscout / openaps 1624 | if [ $(bc <<< "$calibratedBG > 600") -eq 1 -o $(bc <<< "$calibratedBG < 0") -eq 1 ]; then 1625 | if [ "$mode" == "expired" ]; then 1626 | log "Glucose $calibratedBG out of range [0,600] - exiting" 1627 | #state_id=0x07 1628 | #state="Needs Calibration" ; stateString=$state ; stateStringShort=$state 1629 | state_id=0x20 1630 | state="LSR Calibrated BG Out of Bounds" ; stateString=$state ; stateStringShort=$state 1631 | post_cgm_ns_pill 1632 | remove_dexcom_bt_pair 1633 | exit 1634 | fi 1635 | fi 1636 | fi 1637 | 1638 | # Inner Calibrated BG boundary checks for case > 400 1639 | if [ $(bc <<< "$calibratedBG > 400") -eq 1 ]; then 1640 | log "Glucose $calibratedBG over 400; BG value of HI will show in Nightscout" 1641 | calibratedBG=401 1642 | fi 1643 | 1644 | # Inner Calibrated BG boundary checks for case < 40 1645 | if [ $(bc <<< "$calibratedBG < 40") -eq 1 ]; then 1646 | log "Glucose $calibratedBG < 40; BG value of LO will show in Nightscout" 1647 | calibratedBG=39 1648 | fi 1649 | } 1650 | 1651 | function post_cgm_ns_pill() 1652 | { 1653 | # json required conversion to decimal values 1654 | 1655 | if [ -e $calibrationFile ]; then 1656 | lastCalibrationDate=$(stat -c "%Y000" ${calibrationFile}) 1657 | fi 1658 | 1659 | # Dont send tx activation date to NS CGM pill if state is invalid 1660 | if [[ $state_id != 0x25 ]]; then 1661 | txActivation=`date +'%s%3N' -d "$transmitterStartDate"` 1662 | # logic to check if tx age > 90 days and append to state string if so ... 1663 | if [ "$(validBG $glucose)" == "false" -a "$(validBG $unfiltered)" == "false" ]; then 1664 | if [ $(bc -l <<< "($epochdatems - $txActivation)/($SECONDS_IN_1_DAY * 1000) > 90") -eq 1 ]; then 1665 | state="${state}-tx-expired" 1666 | fi 1667 | fi 1668 | fi 1669 | xrig="xdripjs://$(hostname)" 1670 | state_id=$(echo $(($state_id))) 1671 | status_id=$(echo $(($status_id))) 1672 | if [ "$mode" == "read-only" ]; then 1673 | state=$orig_state 1674 | status=$orig_status 1675 | state_id=$orig_state_id 1676 | status_id=$orig_status_id 1677 | fi 1678 | 1679 | jstr="$(build_json \ 1680 | sessionStart "$sessionStartDate" \ 1681 | state "$state_id" \ 1682 | txStatus "$status_id" \ 1683 | stateString "$state" \ 1684 | stateStringShort "$state" \ 1685 | txId "$transmitter" \ 1686 | txActivation "$txActivation" \ 1687 | txStatusString "$status" \ 1688 | txStatusStringShort "$status" \ 1689 | mode "$mode" \ 1690 | timestamp "$epochdatems" \ 1691 | rssi "$rssi" \ 1692 | unfiltered "$unfiltered" \ 1693 | filtered "$filtered" \ 1694 | noise "$noise" \ 1695 | noiseString "$noiseString" \ 1696 | lastCalibrationDate "$lastCalibrationDate" \ 1697 | slope "$slope" \ 1698 | intercept "$yIntercept" \ 1699 | calType "$calibrationType" \ 1700 | batteryTimestamp "$batteryTimestamp" \ 1701 | voltagea "$voltagea" \ 1702 | voltageb "$voltageb" \ 1703 | temperature "$temperature" \ 1704 | resistance "$resist" 1705 | )" 1706 | 1707 | pill="[{\"device\":\"$xrig\",\"xdripjs\": $jstr, \"created_at\":\"$dateString\"}] " 1708 | 1709 | echo $pill > ${LDIR}/cgm-pill.json 1710 | 1711 | /usr/local/bin/cgm-post-ns ${LDIR}/cgm-pill.json devicestatus && (echo; log "Upload to NightScout of cgm status pill record entry worked";) || (echo; log "Upload to NS of cgm status pill record did not work") 1712 | } 1713 | 1714 | function process_delta() 1715 | { 1716 | 1717 | if [ -z $lastGlucose -o $(bc <<< "$lastGlucose < 40") -eq 1 ] ; then 1718 | dg=0 1719 | else 1720 | dg=$(bc <<< "$calibratedBG - $lastGlucose") 1721 | fi 1722 | log "calibratedBG=$calibratedBG, lastGlucose=$lastGlucose, dg=$dg" 1723 | 1724 | # begin try out averaging last two entries ... 1725 | da=$dg 1726 | if [ -n $da -a $(bc <<< "$da < 0") -eq 1 ]; then 1727 | da=$(bc <<< "0 - $da") 1728 | fi 1729 | 1730 | cp ${LDIR}/entry.json ${LDIR}/entry-before-calibration.json 1731 | 1732 | tmp=$(mktemp) 1733 | jq ".[0].glucose = $calibratedBG" ${LDIR}/entry.json > "$tmp" && mv "$tmp" ${LDIR}/entry.json 1734 | 1735 | tmp=$(mktemp) 1736 | jq ".[0].sgv = $calibratedBG" ${LDIR}/entry.json > "$tmp" && mv "$tmp" ${LDIR}/entry.json 1737 | 1738 | tmp=$(mktemp) 1739 | jq ".[0].device = \"${transmitter}\"" ${LDIR}/entry.json > "$tmp" && mv "$tmp" ${LDIR}/entry.json 1740 | 1741 | 1742 | direction='NONE' 1743 | trend=0 1744 | 1745 | # Begin trend calculation logic based on last 15 minutes glucose delta average 1746 | if [ -z "$dg" ]; then 1747 | direction="NONE" 1748 | log "setting direction=NONE because dg is null, dg=$dg" 1749 | else 1750 | usedRecords=0 1751 | totalDelta=0 1752 | # Don't use files to store delta's anymore. Use monitor/glucose.json in order to 1753 | # be able to support multiple rigs running openaps / Logger at same time. 1754 | 1755 | # after=$(date -d "15 minutes ago" -Iminutes) 1756 | # glucosejqstr="'[ .[] | select(.dateString > \"$after\") ]'" 1757 | epms15=$(bc -l <<< "$epochdate *1000 - 900000") 1758 | glucosejqstr="'[ .[] | select(.date > $epms15) ]'" 1759 | bash -c "jq -c $glucosejqstr ~/myopenaps/monitor/glucose.json" > ${LDIR}/last15minutes.json 1760 | last3=( $(jq -r ".[].sgv" ${LDIR}/last15minutes.json) ) 1761 | date3=( $(jq -r ".[].date" ${LDIR}/last15minutes.json) ) 1762 | #log ${last3[@]} 1763 | 1764 | usedRecords=${#last3[@]} 1765 | totalDelta=$dg 1766 | 1767 | for (( i=1; i<$usedRecords; i++ )) 1768 | do 1769 | #log "before totalDelta=$totalDelta, last3[i-1]=${last3[$i-1]}, last3[i]=${last3[$i]}" 1770 | if [ $(bc <<< "${last3[$i]} > 20") -eq 1 -a $(bc <<< "${last3[$i-1]} > 20") -eq 1 ]; then 1771 | totalDelta=$(bc <<< "$totalDelta + (${last3[$i-1]} - ${last3[$i]})") 1772 | #log "after totalDelta=$totalDelta" 1773 | else 1774 | # for null/bad glucose value in last 3 - leave trend to default which is none or unkown 1775 | usedRecords=0 1776 | fi 1777 | done 1778 | 1779 | if [ $(bc <<< "$usedRecords > 0") -eq 1 ]; then 1780 | numMinutes=$(bc -l <<< "($epochdate-(${date3[$usedRecords-1]}/1000))/60") 1781 | perMinuteAverageDelta=$(bc -l <<< "$totalDelta / $numMinutes") 1782 | log "direction calculation based on $numMinutes minutes" 1783 | log "perMinuteAverageDelta=$perMinuteAverageDelta" 1784 | 1785 | if (( $(bc <<< "$perMinuteAverageDelta > 3") )); then 1786 | direction='DoubleUp' 1787 | trend=1 1788 | elif (( $(bc <<< "$perMinuteAverageDelta > 2") )); then 1789 | direction='SingleUp' 1790 | trend=2 1791 | elif (( $(bc <<< "$perMinuteAverageDelta > 1") )); then 1792 | direction='FortyFiveUp' 1793 | trend=3 1794 | elif (( $(bc <<< "$perMinuteAverageDelta < -3") )); then 1795 | direction='DoubleDown' 1796 | trend=7 1797 | elif (( $(bc <<< "$perMinuteAverageDelta < -2") )); then 1798 | direction='SingleDown' 1799 | trend=6 1800 | elif (( $(bc <<< "$perMinuteAverageDelta < -1") )); then 1801 | direction='FortyFiveDown' 1802 | trend=5 1803 | else 1804 | direction='Flat' 1805 | trend=4 1806 | fi 1807 | fi 1808 | fi 1809 | 1810 | log "perMinuteAverageDelta=$perMinuteAverageDelta, totalDelta=$totalDelta, usedRecords=$usedRecords" 1811 | log "Gluc=${calibratedBG}, last=${lastGlucose}, diff=${dg}, dir=${direction}" 1812 | 1813 | cat ${LDIR}/entry.json | jq ".[0].direction = \"$direction\"" > ${LDIR}/entry-xdrip.json 1814 | 1815 | tmp=$(mktemp) 1816 | jq ".[0].trend = $trend" ${LDIR}/entry-xdrip.json > "$tmp" && mv "$tmp" ${LDIR}/entry-xdrip.json 1817 | } 1818 | 1819 | function calculate_noise() 1820 | { 1821 | noise_input="${LDIR}/noise-input41.csv" 1822 | truncate -s 0 ${noise_input} 1823 | 1824 | # calculate the noise and position it for updating the entry sent to NS and xdripAPS 1825 | # get last 41 minutes (approx 7 BG's) from monitor/glucose to better support multiple rigs 1826 | # be able to support multiple rigs running openaps / Logger at same time. 1827 | epms41=$(bc -l <<< "$epochdate *1000 - 41*60000") 1828 | glucosejqstr="'[ .[] | select(.date > $epms41) | select(.unfiltered > 0) ]'" 1829 | bash -c "jq -c $glucosejqstr ~/myopenaps/monitor/glucose.json" > ${LDIR}/last41minutes.json 1830 | date41=( $(jq -r ".[].date" ${LDIR}/last41minutes.json) ) 1831 | gluc41=( $(jq -r ".[].glucose" ${LDIR}/last41minutes.json) ) 1832 | unf41=( $(jq -r ".[].unfiltered" ${LDIR}/last41minutes.json) ) 1833 | fil41=( $(jq -r ".[].filtered" ${LDIR}/last41minutes.json) ) 1834 | 1835 | usedRecords=${#gluc41[@]} 1836 | log "usedRecords=$usedRecords last 41 minutes = ${gluc41[@]}" 1837 | 1838 | for (( i=$usedRecords-1; i>=0; i-- )) 1839 | do 1840 | dateSeconds=$(bc <<< "${date41[$i]} / 1000") 1841 | echo "$dateSeconds,${unf41[$i]},${fil41[$i]},${gluc41[$i]}" >> ${noise_input} 1842 | done 1843 | echo "${epochdate},${unfiltered},${filtered},${calibratedBG}" >> ${noise_input} 1844 | 1845 | cgm-calc-noise ${noise_input} 1846 | 1847 | if [ -e ${LDIR}/noise.json ]; then 1848 | noise=`jq -M '.[0] .noise' ${LDIR}/noise.json` 1849 | noiseSend=`jq -M '.[0] .noiseSend' ${LDIR}/noise.json` 1850 | noiseString=`jq -M '.[0] .noiseString' ${LDIR}/noise.json` 1851 | noiseString="${noiseString%\"}" 1852 | noiseString="${noiseString#\"}" 1853 | # remove issue where jq returns scientific notation, convert to decimal 1854 | noise=$(awk -v noise="$noise" 'BEGIN { printf("%.2f", noise) }' "$tmp" && mv "$tmp" ${LDIR}/entry-xdrip.json 1872 | } 1873 | 1874 | function check_messages() 1875 | { 1876 | cgm_stop_file="${LDIR}/cgm-stop.json" 1877 | if [ -e "$cgm_stop_file" ]; then 1878 | stopJSON=$(cat $cgm_stop_file) 1879 | log "stopJSON=$stopJSON" 1880 | # Always clear LSR cache for any new firmware g6 start / stop 1881 | if [ "$(newFirmware $tx_version)" == "true" ]; then 1882 | ClearCalibrationInput 1883 | ClearCalibrationCache 1884 | fi 1885 | # wait to remove command line file after call_logger (Tx/Rx processing) 1886 | fi 1887 | 1888 | cgm_start_file="${LDIR}/cgm-start.json" 1889 | if [ -e "$cgm_start_file" ]; then 1890 | startJSON=$(cat $cgm_start_file) 1891 | log "startJSON=$startJSON" 1892 | # Always clear LSR cache for any new firmware g6 start / stop 1893 | if [ "$(newFirmware $tx_version)" == "true" ]; then 1894 | ClearCalibrationInput 1895 | ClearCalibrationCache 1896 | fi 1897 | #TODO: add cmd line treatments to NS 1898 | 1899 | # wait to remove command line file after call_logger (Tx/Rx processing) 1900 | fi 1901 | 1902 | file="${LDIR}/cgm-reset.json" 1903 | if [ -e "$file" ]; then 1904 | resetJSON=$(cat $file) 1905 | log "resetJSON=$resetJSON" 1906 | rm -f $file 1907 | fi 1908 | 1909 | file="${LDIR}/cgm-version.json" 1910 | if [ -e "$file" ]; then 1911 | versionJSON=$(cat $file) 1912 | log "versionJSON=$versionJSON" 1913 | rm -f $file 1914 | fi 1915 | } 1916 | 1917 | function check_recent_sensor_insert() 1918 | { 1919 | if [ "$mode" == "read-only" ]; then 1920 | return 1921 | fi 1922 | 1923 | # check if sensor inserted in last 12 hours. 1924 | # If so, clear calibration inputs and only calibrate using single point calibration 1925 | # do not keep the calibration records within the first 12 hours as they might skew LSR 1926 | if [ -e ${LDIR}/last_sensor_change ]; then 1927 | if test `find ${LDIR}/last_sensor_change -mmin -720` 1928 | then 1929 | log "sensor change within last 12 hours - will use single pt calibration" 1930 | ClearCalibrationInputOne 1931 | fi 1932 | fi 1933 | } 1934 | 1935 | function post-nightscout-with-backfill() 1936 | { 1937 | #if [ "$state" == "Stopped" ]; then 1938 | # don't post glucose to NS 1939 | #return 1940 | #fi 1941 | 1942 | 1943 | if [ -e "${LDIR}/entry-backfill2.json" ] ; then 1944 | /usr/local/bin/cgm-post-ns ${LDIR}/entry-backfill2.json && (echo; log "Upload backfill2 to NightScout worked ... removing ${LDIR}/entry-backfill2.json"; rm -f ${LDIR}/entry-backfill2.json) || (echo; log "Upload backfill to NS did not work ... keeping for upload when network is restored ... Auth to NS may have failed; ensure you are using hashed API_SECRET in ~/.bash_profile";) 1945 | fi 1946 | 1947 | if [ -e "${LDIR}/entry-backfill.json" ] ; then 1948 | # In this case backfill records not yet sent to Nightscout 1949 | jq -s add ${LDIR}/entry-xdrip.json ${LDIR}/entry-backfill.json > ${LDIR}/entry-ns.json 1950 | cp ${LDIR}/entry-ns.json ${LDIR}/entry-backfill.json 1951 | log "${LDIR}/entry-backfill.json exists, so setting up for backfill" 1952 | else 1953 | log "${LDIR}/entry-backfill.json does not exist so no backfill" 1954 | cp ${LDIR}/entry-xdrip.json ${LDIR}/entry-ns.json 1955 | fi 1956 | 1957 | log "Posting blood glucose to NightScout" 1958 | /usr/local/bin/cgm-post-ns ${LDIR}/entry-ns.json && (echo; log "Upload to NightScout of xdrip entry worked ... removing ${LDIR}/entry-backfill.json"; rm -f ${LDIR}/entry-backfill.json) || (echo; log "Upload to NS of xdrip entry did not work ... saving for upload when network is restored ... Auth to NS may have failed; ensure you are using hashed API_SECRET in ~/.bash_profile"; cp ${LDIR}/entry-ns.json ${LDIR}/entry-backfill.json) 1959 | echo 1960 | 1961 | postTreatmentsToNS 1962 | } 1963 | 1964 | function postTreatmentsToNS() 1965 | { 1966 | if [ -e "$treatmentsFile" ]; then 1967 | log "Posting treatments to NightScout" 1968 | /usr/local/bin/cgm-post-ns $treatmentsFile treatments && (echo; log "Upload to NightScout of xdrip treatments worked ... removing $treatmentsFile"; rm -f $treatmentsFile) || (echo; log "Upload to NS of xdrip entry did not work ... saving treatments for upload when network is restored ... Auth to NS may have failed; ensure you are using hashed API_SECRET in ~/.bash_profile") 1969 | echo 1970 | fi 1971 | } 1972 | 1973 | function wait_with_echo() 1974 | { 1975 | total_wait_remaining=$1 1976 | waited_so_far=0 1977 | 1978 | while [ $(bc <<< "$total_wait_remaining >= 10") -eq 1 ] 1979 | do 1980 | echo -n "." 1981 | sleep 10 1982 | total_wait_remaining=$(bc <<< "$total_wait_remaining - 10") 1983 | done 1984 | 1985 | if [ $(bc <<< "$total_wait_remaining >= 1") -eq 1 ]; then 1986 | sleep $total_wait_remaining 1987 | fi 1988 | echo 1989 | log "Wait complete" 1990 | } 1991 | 1992 | function check_last_glucose_time_smart_sleep() 1993 | { 1994 | rm -f $xdripMessageFile 1995 | 1996 | if [ -e ${LDIR}/entry-watchdog ]; then 1997 | entry_timestamp=$(date -r ${LDIR}/entry-watchdog +'%s') 1998 | seconds_since_last_entry=$(bc <<< "$epochdate - $entry_timestamp") 1999 | log "check_last_glucose_time - epochdate=$epochdate, entry_timestamp=$entry_timestamp" 2000 | log "Time since last glucose entry in seconds = $seconds_since_last_entry seconds" 2001 | sleep_time=$(bc <<< "180 - $seconds_since_last_entry") 2002 | if [ $(bc <<< "$sleep_time > 0") -eq 1 -a $(bc <<< "$sleep_time < 180") -eq 1 ]; then 2003 | log "Waiting $sleep_time seconds because glucose records only happen every 5 minutes" 2004 | wait_with_echo $sleep_time 2005 | elif [ $(bc <<< "$sleep_time < -60") -eq 1 -a $(bc <<< "$sleep_time > -3660") -eq 1 ]; then 2006 | # Don't backfill more than 1 hour back to avoid timeouts 2007 | # FIXME: maybe this sholud go in a seperate function not related to sleep 2008 | backfill_start=${lastGlucoseDate} 2009 | [[ $backfill_start == 0 ]] && backfill_start=$(date "+%s%3N" -d @"$entry_timestamp") 2010 | # add one minute to the backfill_start to avoid duplicating the last seen entry 2011 | backfill_start=$(bc <<< "$backfill_start + 60 * 1000") 2012 | 2013 | log "Adding backfill message since $backfill_start" 2014 | addToMessages "[{\"date\":\"${backfill_start}\",\"type\":\"Backfill\"}]" $xdripMessageFile 2015 | fi 2016 | else 2017 | log "More than 4 minutes since last glucose entry, continue processing without waiting" 2018 | fi 2019 | } 2020 | 2021 | function check_sensitivity() 2022 | { 2023 | sensitivity=$(jq .ratio ${HOME}/myopenaps/settings/autosens.json) 2024 | } 2025 | 2026 | function bt_watchdog() 2027 | { 2028 | logfiledir=/var/log/openaps 2029 | logfilename=logger-reset-log.txt 2030 | minutes=19 2031 | xdrip_errors=`find ${LDIR} -mmin -$minutes -type f -name entry-watchdog; find $logfiledir -mmin -$minutes -type f -name $logfilename` 2032 | if [ -z "$xdrip_errors" ] 2033 | then 2034 | logfile=$logfiledir/$logfilename 2035 | date >> $logfile 2036 | echo "no entry.json for $minutes minutes" | tee -a $logfile 2037 | if [[ "$watchdog" == true ]]; then 2038 | echo "Rebooting" | tee -a $logfile 2039 | wall "Rebooting in 15 seconds to fix BT and xdrip-js - save your work quickly!" 2040 | cd ${HOME}/myopenaps && /etc/init.d/cron stop && killall -g openaps ; killall -g oref0-pump-loop | tee -a $logfile 2041 | sleep 15 2042 | reboot 2043 | else 2044 | echo "Not rebooting because watchdog preference is false" | tee -a $logfile 2045 | fi 2046 | fi 2047 | } 2048 | 2049 | function round() 2050 | { 2051 | echo "$1" | awk '{printf("%d\n",$1 + 0.5)}' 2052 | } 2053 | 2054 | 2055 | main "$@" 2056 | --------------------------------------------------------------------------------