├── .gitignore ├── Dockerfile ├── IExporter.go ├── Exporter.go ├── go.mod ├── docker-compose.yml ├── README.md ├── NSClient.go ├── nsentry.go ├── MongoClient.go ├── main.go ├── go.sum └── grafana.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.18.2-alpine AS build 2 | 3 | WORKDIR /app 4 | COPY *.go go.* ./ 5 | 6 | RUN CGO_ENABLED=0 go build -o /ns-exporter . 7 | 8 | FROM djpic/cron:standard 9 | 10 | COPY --from=build /ns-exporter /etc/periodic/1min/ns-exporter 11 | 12 | RUN chmod 755 /etc/periodic/1min/ns-exporter -------------------------------------------------------------------------------- /IExporter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type IExporter interface { 8 | Authorize(ctx context.Context) 9 | LoadDeviceStatuses(queue chan NsEntry, limit int64, skip int64, ctx context.Context) 10 | LoadTreatments(queue chan NsTreatment, limit int64, skip int64, ctx context.Context) 11 | Close(ctx context.Context) 12 | } 13 | -------------------------------------------------------------------------------- /Exporter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Exporter struct { 8 | client IExporter 9 | } 10 | 11 | func NewExporterFromMongo(uri string, db string, user string, ctx context.Context) *Exporter { 12 | exporter := &Exporter{ 13 | client: NewMongoClient(uri, db, user, ctx), 14 | } 15 | return exporter 16 | } 17 | 18 | func NewExporterFromNS(uri string, token string, user string) *Exporter { 19 | exporter := &Exporter{ 20 | client: NewNSClient(uri, token, user), 21 | } 22 | return exporter 23 | } 24 | 25 | func (worker Exporter) processClient(deviceStatuses chan NsEntry, treatments chan NsTreatment, limit int64, skip int64, ctx context.Context) { 26 | worker.client.Authorize(ctx) 27 | wg.Add(2) 28 | go worker.client.LoadDeviceStatuses(deviceStatuses, limit, skip, ctx) 29 | go worker.client.LoadTreatments(treatments, limit, skip, ctx) 30 | } 31 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module ns-exporter 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/go-resty/resty/v2 v2.7.0 7 | github.com/influxdata/influxdb-client-go/v2 v2.9.0 8 | github.com/peterbourgon/ff/v3 v3.1.2 9 | go.mongodb.org/mongo-driver v1.9.1 10 | ) 11 | 12 | require ( 13 | github.com/deepmap/oapi-codegen v1.8.2 // indirect 14 | github.com/go-stack/stack v1.8.0 // indirect 15 | github.com/golang/snappy v0.0.1 // indirect 16 | github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect 17 | github.com/klauspost/compress v1.13.6 // indirect 18 | github.com/pkg/errors v0.9.1 // indirect 19 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 20 | github.com/xdg-go/scram v1.0.2 // indirect 21 | github.com/xdg-go/stringprep v1.0.2 // indirect 22 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 23 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect 24 | golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect 25 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect 26 | golang.org/x/text v0.3.6 // indirect 27 | gopkg.in/yaml.v2 v2.3.0 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | services: 3 | grafana-ns: 4 | image: grafana/grafana-oss:latest 5 | container_name: grafana-ns 6 | restart: always 7 | depends_on: 8 | - influx-ns 9 | volumes: 10 | - data-grafana-ns:/var/lib/grafana 11 | networks: 12 | - ns-network 13 | ports: 14 | - 3000:3000 15 | 16 | influx-ns: 17 | image: influxdb:latest 18 | container_name: influx-ns 19 | restart: always 20 | volumes: 21 | - data-influx-ns:/var/lib/influxdb2 22 | networks: 23 | - ns-network 24 | ports: 25 | - 8086:8086 26 | 27 | # mongo-ns: 28 | # image: mongo:latest 29 | # container_name: mongo-ns 30 | # volumes: 31 | # - data-mongo-ns:/data/db 32 | # networks: 33 | # - ns-network 34 | # ports: 35 | # - 27017:27017 36 | 37 | ns-exporter: 38 | image: ns-exporter:latest 39 | container_name: ns-exporter 40 | restart: unless-stopped 41 | environment: 42 | - NS_EXPORTER_MONGO_URI=${NS_EXPORTER_MONGO_URI:-mongodb://mongo-ns:27017} 43 | - NS_EXPORTER_MONGO_DB=${NS_EXPORTER_MONGO_DB:-ns} 44 | - NS_EXPORTER_INFLUX_URI=${NS_EXPORTER_INFLUX_URI:-http://influx-ns:8086} 45 | - NS_EXPORTER_INFLUX_TOKEN=${NS_EXPORTER_INFLUX_TOKEN?err} 46 | - NS_EXPORTER_LIMIT=3 47 | - NS_EXPORTER_SKIP=0 48 | depends_on: 49 | - influx-ns 50 | # - mongo 51 | networks: 52 | - ns-network 53 | 54 | networks: 55 | ns-network: 56 | driver: bridge 57 | volumes: 58 | data-grafana-ns: 59 | data-influx-ns: 60 | # data-mongo-ns: 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ns-exporter 2 | Nightscout exporter to InfluxDB 3 | 4 | ### usage variants: 5 | 1. inline 6 | ``` 7 | go build 8 | ./ns-exporter 9 | ``` 10 | 2. docker 11 | ``` 12 | docker build -t ns-exporter . 13 | docker run -d ns-exporter:latest 14 | ``` 15 | 16 | arguments: 17 | 18 | mongo-uri - MongoDb uri to download from 19 | mongo-db - MongoDb database name 20 | ns-uri - Nightscout server url to download from 21 | ns-token - Nigthscout server API Authorization Token 22 | limit - number of records to read from MongoDb 23 | skip - number of records to skip from MongoDb 24 | influx-uri - InfluxDb uri to download from 25 | influx-token - InfluxDb access token 26 | influx-org - (optional, default = 'ns') InfluxDb organization to use 27 | influx-bucket - (optional, default = 'ns') InfluxDb bucket to use 28 | influx-user-tag - (optional, default = 'unknown') InfluxDb 'user' tag value to be added to every record - to be able to store multiple users data in single bucket 29 | 30 | 31 | arguments also can be provided via env with `NS_EXPORTER_` prefix: 32 | 33 | NS_EXPORTER_MONGO_URI= 34 | NS_EXPORTER_MONGO_DB= 35 | NS_EXPORTER_NS_URI= 36 | NS_EXPORTER_NS_TOKEN= 37 | NS_EXPORTER_LIMIT= 38 | NS_EXPORTER_SKIP= 39 | NS_EXPORTER_INFLUX_URI= 40 | NS_EXPORTER_INFLUX_TOKEN= 41 | NS_EXPORTER_INFLUX_ORG= 42 | NS_EXPORTER_INFLUX_BUCKET= 43 | NS_EXPORTER_INFLUX_USER_TAG= 44 | 45 | So you can choose the data source: direct MongoDB or Nightscout REST API. Supplying required set of parameters will trigger related consumer. 46 | You can even supply both and get from both sources :) 47 | 48 | For NS API access you need provide security token. For security reason it is better to go to 'Admin tools' and create special token for NS-Exporter only instead of using admin security key. 49 | Since exporter only requires read access, creating role with two permissions will be enough: 50 | - api:treatments:read 51 | - api:devicestatus:read 52 | 53 | ### Presentation 54 | 55 | I'm using Grafana dashboard for viewing data. To setup grafana with InfluxDB you need to follow InfluxDB's [instructions](https://docs.influxdata.com/influxdb/v2.3/tools/grafana/). 56 | The sample dashboard can be imported from `grafana.json`. It uses both InfluxQL and Flux datasources for different panels. Some can be omitted, some can be reworker based on other InfluxDB datasource query type. 57 | Anyway they're provided as samples, for educational purpose :) -------------------------------------------------------------------------------- /NSClient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | "strings" 9 | ) 10 | import "github.com/go-resty/resty/v2" 11 | 12 | type NSClient struct { 13 | nsUri string 14 | nsToken string 15 | user string 16 | jwt string 17 | } 18 | 19 | type nsDeviceStatusResult struct { 20 | Status int `json:"status"` 21 | Records []NsEntry `json:"result"` 22 | } 23 | type nsTreatmentsResult struct { 24 | Status int `json:"status"` 25 | Records []NsTreatment `json:"result"` 26 | } 27 | type nsJwtResult struct { 28 | Token string `json:"token"` 29 | } 30 | 31 | func NewNSClient(uri string, token string, user string) *NSClient { 32 | return &NSClient{ 33 | nsUri: strings.TrimRight(uri, "/"), 34 | nsToken: token, 35 | user: user, 36 | } 37 | } 38 | 39 | func (c *NSClient) Authorize(_ context.Context) { 40 | client := resty.New() 41 | result := &nsJwtResult{} 42 | _, err := client.R(). 43 | SetResult(result). 44 | SetHeader("Accept", "application/json"). 45 | Get(c.nsUri + "/api/v2/authorization/request/" + c.nsToken) 46 | 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | c.jwt = result.Token 51 | } 52 | 53 | func (c *NSClient) LoadDeviceStatuses(queue chan NsEntry, limit int64, skip int64, _ context.Context) { 54 | defer wg.Done() 55 | 56 | fmt.Println("LoadDeviceStatuses from NS, limit: ", limit, ", skip: ", skip) 57 | 58 | client := resty.New() 59 | 60 | entries := &nsDeviceStatusResult{} 61 | _, err := client.R(). 62 | SetQueryParams(map[string]string{ 63 | "skip": strconv.FormatInt(skip, 10), 64 | "limit": strconv.FormatInt(limit, 10), 65 | "sort$desc": "created_at", 66 | }). 67 | SetAuthScheme("Bearer"). 68 | SetAuthToken(c.jwt). 69 | SetHeader("Accept", "application/json"). 70 | SetResult(entries). 71 | Get(c.nsUri + "/api/v3/devicestatus") 72 | 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | 77 | for _, entry := range entries.Records { 78 | if strings.HasPrefix(entry.Device, "openaps") { 79 | entry.User = c.user 80 | queue <- entry 81 | } 82 | } 83 | } 84 | 85 | func (c *NSClient) LoadTreatments(queue chan NsTreatment, limit int64, skip int64, _ context.Context) { 86 | defer wg.Done() 87 | 88 | fmt.Println("LoadTreatments from NS, limit: ", limit, ", skip: ", skip) 89 | 90 | client := resty.New() 91 | 92 | entries := &nsTreatmentsResult{} 93 | _, err := client.R(). 94 | SetQueryParams(map[string]string{ 95 | "skip": strconv.FormatInt(skip, 10), 96 | "limit": strconv.FormatInt(limit, 10), 97 | "sort$desc": "created_at", 98 | }). 99 | SetResult(entries). 100 | SetHeader("Accept", "application/json"). 101 | SetAuthScheme("Bearer"). 102 | SetAuthToken(c.jwt). 103 | Get(c.nsUri + "/api/v3/treatments") 104 | 105 | if err != nil { 106 | log.Fatal(err) 107 | } 108 | for _, entry := range entries.Records { 109 | entry.User = c.user 110 | queue <- entry 111 | } 112 | } 113 | 114 | func (c *NSClient) Close(_ context.Context) {} 115 | -------------------------------------------------------------------------------- /nsentry.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "time" 4 | 5 | type NsEntry struct { 6 | Device string 7 | OpenAps struct { 8 | Suggested struct { 9 | Temp string `json:"temp" bson:"temp"` 10 | Bg float64 `json:"bg" bson:"bg"` 11 | Tick float64 `json:"-" bson:"-"` 12 | EventualBG float64 `json:"eventualBG" bson:"eventualBG"` 13 | TargetBG float64 `json:"targetBG" bson:"targetBG"` 14 | InsulinReq float64 `json:"insulinReq" bson:"insulinReq"` 15 | DeliverAt time.Time `json:"deliverAt" bson:"deliverAt"` 16 | SensitivityRatio float64 `json:"sensitivityRatio" bson:"sensitivityRatio"` 17 | PredBGs struct { 18 | IOB []float64 `json:"IOB"` 19 | ZT []float64 `json:"ZT"` 20 | COB []float64 `json:"COB"` 21 | UAM []float64 `json:"UAM"` 22 | } `json:"predBGs"` 23 | COB float64 `json:"COB"` 24 | IOB float64 `json:"IOB"` 25 | Reason string `json:"reason"` 26 | Units float64 `json:"units"` 27 | Rate float64 `json:"rate"` 28 | Duration int `json:"duration"` 29 | Timestamp time.Time `json:"timestamp"` 30 | } `json:"suggested,omitempty" bson:"suggested,omitempty"` 31 | IOB struct { 32 | IOB float64 `json:"iob" bson:"iob"` 33 | BasalIOB float64 `json:"basaliob" bson:"basaliob"` 34 | Activity float64 `json:"activity" bson:"activity"` 35 | Time time.Time `json:"time" bson:"time"` 36 | } `json:"iob" bson:"iob"` 37 | } `json:"openaps" bson:"openaps"` 38 | Pump struct { 39 | Clock time.Time `json:"clock"` 40 | Reservoir float64 `json:"reservoir"` 41 | Status struct { 42 | Status string `json:"status"` 43 | Timestamp int64 `json:"-" bson:"-"` 44 | } `json:"status"` 45 | Extended struct { 46 | Version string `json:"Version"` 47 | ActiveProfile string `json:"ActiveProfile"` 48 | TempBasalAbsoluteRate float64 `json:"TempBasalAbsoluteRate"` 49 | TempBasalPercent int `json:"TempBasalPercent"` 50 | TempBasalRemaining int `json:"TempBasalRemaining"` 51 | } `json:"extended"` 52 | Battery struct { 53 | Percent int `json:"percent"` 54 | } `json:"battery"` 55 | } `json:"pump"` 56 | User string `json:"-"` 57 | } 58 | 59 | type NsTreatment struct { 60 | CreatedAt time.Time `json:"created_at"` 61 | EnteredBy string `json:"enteredBy"` 62 | EventType string `json:"eventType"` 63 | Carbs int `json:"carbs,omitempty"` 64 | Duration int `json:"duration,omitempty"` 65 | Insulin float64 `json:"insulin,omitempty"` 66 | IsSMB bool `json:"isSMB,omitempty"` 67 | Notes string `json:"notes,omitempty"` 68 | Percent int `json:"percent,omitempty"` 69 | TargetTop float64 `json:"targetTop,omitempty"` 70 | TargetBottom float64 `json:"targetBottom,omitempty"` 71 | Reason string `json:"reason,omitempty"` 72 | Rate float64 `json:"rate,omitempty"` 73 | Units string `json:"units,omitempty"` 74 | User string `json:"-"` 75 | } 76 | 77 | type Config struct { 78 | NsUri string `json:"ns-uri,omitempty"` 79 | NsToken string `json:"ns-token,omitempty"` 80 | MongoUri string `json:"mongo-uri,omitempty"` 81 | MongoDb string `json:"mongo-db,omitempty"` 82 | Limit int64 `json:"limit,omitempty"` 83 | Skip int64 `json:"skip,omitempty"` 84 | InfluxUri string `json:"influx-uri,omitempty"` 85 | InfluxToken string `json:"influx-token,omitempty"` 86 | InfluxOrg string `json:"influx-org,omitempty"` 87 | InfluxBucket string `json:"influx-bucket,omitempty"` 88 | Imports []struct { 89 | NsUri string `json:"ns-uri,omitempty"` 90 | NsToken string `json:"ns-token,omitempty"` 91 | MongoUri string `json:"mongo-uri,omitempty"` 92 | MongoDb string `json:"mongo-db,omitempty"` 93 | User string `json:"user"` 94 | } `json:"imports,omitempty"` 95 | } 96 | -------------------------------------------------------------------------------- /MongoClient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "go.mongodb.org/mongo-driver/bson" 7 | "go.mongodb.org/mongo-driver/bson/bsontype" 8 | "go.mongodb.org/mongo-driver/mongo" 9 | "go.mongodb.org/mongo-driver/mongo/options" 10 | "log" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | type MongoClient struct { 16 | mongoUri string 17 | mongoDb string 18 | db *mongo.Database 19 | client *mongo.Client 20 | user string 21 | } 22 | 23 | func NewMongoClient(uri string, db string, user string, ctx context.Context) *MongoClient { 24 | c := &MongoClient{ 25 | mongoUri: uri, 26 | mongoDb: db, 27 | user: user, 28 | } 29 | 30 | client, err := mongo.NewClient(options.Client().ApplyURI(c.mongoUri)) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | c.client = client 35 | 36 | err = client.Connect(ctx) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | 41 | err = client.Ping(ctx, nil) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | c.db = client.Database(c.mongoDb) 47 | return c 48 | } 49 | 50 | func (c *MongoClient) Authorize(_ context.Context) {} 51 | 52 | func (c *MongoClient) LoadDeviceStatuses(queue chan NsEntry, limit int64, skip int64, ctx context.Context) { 53 | 54 | defer wg.Done() 55 | 56 | fmt.Println("LoadDeviceStatuses from MongoDB, limit: ", limit, ", skip: ", skip) 57 | 58 | collection := c.db.Collection("devicestatus") 59 | filter := bson.D{{"openaps", bson.D{{"$exists", true}}}} 60 | 61 | opts := options.Find() 62 | opts.SetSort(bson.D{{"created_at", -1}}) 63 | if limit > 0 { 64 | opts.SetLimit(limit) 65 | } 66 | if skip > 0 { 67 | opts.SetSkip(skip) 68 | } 69 | 70 | cur, err := collection.Find(ctx, filter, opts) 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | defer cur.Close(ctx) 75 | 76 | var count = 0 77 | for cur.Next(ctx) { 78 | var entry NsEntry 79 | err := cur.Decode(&entry) 80 | if err != nil { 81 | fmt.Println(cur.Current.String()) 82 | log.Fatal(err) 83 | } 84 | entry.User = c.user 85 | if entry.OpenAps.Suggested.Bg > 0 { 86 | field := cur.Current.Lookup("openaps", "suggested", "tick") 87 | var tick float64 = 0 88 | if field.Type == bsontype.String { 89 | tick, err = strconv.ParseFloat(field.StringValue(), 32) 90 | } 91 | if field.Type == bsontype.Int32 { 92 | tick = float64(field.AsInt64()) 93 | } 94 | entry.OpenAps.Suggested.Tick = tick 95 | } 96 | 97 | queue <- entry 98 | 99 | count++ 100 | 101 | fmt.Println("devicestatus time: ", entry.OpenAps.IOB.Time, "iob:", entry.OpenAps.IOB.IOB, ", bg: ", entry.OpenAps.Suggested.Bg) 102 | } 103 | fmt.Println("total devicestatuses sent: ", count) 104 | if err := cur.Err(); err != nil { 105 | log.Fatal(err) 106 | } 107 | } 108 | 109 | func (c *MongoClient) LoadTreatments(queue chan NsTreatment, limit int64, skip int64, ctx context.Context) { 110 | defer wg.Done() 111 | 112 | fmt.Println("LoadTreatments from MongoDB, limit: ", limit, ", skip: ", skip) 113 | collection := c.db.Collection("treatments") 114 | filter := bson.D{} 115 | 116 | opts := options.Find() 117 | opts.SetSort(bson.D{{"created_at", -1}}) 118 | if limit > 0 { 119 | opts.SetLimit(limit) 120 | } 121 | if skip > 0 { 122 | opts.SetSkip(skip) 123 | } 124 | 125 | cur, err := collection.Find(ctx, filter, opts) 126 | if err != nil { 127 | log.Fatal(err) 128 | } 129 | defer cur.Close(ctx) 130 | 131 | var count = 0 132 | for cur.Next(ctx) { 133 | var entry NsTreatment 134 | err := cur.Decode(&entry) 135 | if err != nil { 136 | log.Fatal(err) 137 | } 138 | entry.User = c.user 139 | strtime := cur.Current.Lookup("created_at").StringValue() 140 | ptime, err := time.Parse(time.RFC3339, strtime) 141 | if err != nil { 142 | log.Fatal(err) 143 | } 144 | 145 | entry.CreatedAt = ptime 146 | 147 | queue <- entry 148 | count++ 149 | 150 | fmt.Println("treatment time: ", entry.CreatedAt, ", type: ", entry.EventType) 151 | } 152 | 153 | fmt.Println("total treatments sent: ", count) 154 | if err := cur.Err(); err != nil { 155 | log.Fatal(err) 156 | } 157 | } 158 | 159 | func (c *MongoClient) Close(ctx context.Context) { 160 | c.client.Disconnect(ctx) 161 | } 162 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | influxdb2 "github.com/influxdata/influxdb-client-go/v2" 9 | "github.com/influxdata/influxdb-client-go/v2/api/write" 10 | "github.com/peterbourgon/ff/v3" 11 | "html" 12 | "log" 13 | "os" 14 | "regexp" 15 | "strconv" 16 | "sync" 17 | ) 18 | 19 | var wg sync.WaitGroup 20 | var wgInflux sync.WaitGroup 21 | 22 | func main() { 23 | fs := flag.NewFlagSet("ns-exporter", flag.ContinueOnError) 24 | var ( 25 | mongoUri = fs.String("mongo-uri", "", "Mongo-db uri to download from") 26 | mongoDb = fs.String("mongo-db", "", "Mongo-db database name") 27 | nsUri = fs.String("ns-uri", "", "Nightscout server url to download from") 28 | nsToken = fs.String("ns-token", "", "Nigthscout server API Authorization Token") 29 | limit = fs.Int64("limit", 0, "number of records to read from mongo-db") 30 | skip = fs.Int64("skip", 0, "number of records to skip from mongo-db") 31 | influxUri = fs.String("influx-uri", "", "InfluxDb uri to download from") 32 | influxToken = fs.String("influx-token", "", "InfluxDb access token") 33 | influxOrg = fs.String("influx-org", "ns", "InfluxDb organization to use") 34 | influxBucket = fs.String("influx-bucket", "ns", "InfluxDb bucket to use") 35 | configFile = fs.String("config", "", "File to load configuration from") 36 | user = fs.String("user", "", "User name to be set on Influx record") 37 | ) 38 | if err := ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix("NS_EXPORTER")); err != nil { 39 | fmt.Fprintf(os.Stderr, "error: %v\n", err) 40 | os.Exit(1) 41 | } 42 | 43 | ctx := context.Background() 44 | 45 | deviceStatuses := make(chan NsEntry) 46 | treatments := make(chan NsTreatment) 47 | influx := make(chan write.Point) 48 | 49 | if *mongoUri != "" && *mongoDb != "" { 50 | NewExporterFromMongo(*mongoUri, *mongoDb, *user, ctx).processClient(deviceStatuses, treatments, *limit, *skip, ctx) 51 | } 52 | if *nsUri != "" && *nsToken != "" { 53 | NewExporterFromNS(*nsUri, *nsToken, *user).processClient(deviceStatuses, treatments, *limit, *skip, ctx) 54 | } 55 | var config = Config{} 56 | if *configFile != "" { 57 | file, err := os.Open(*configFile) 58 | if err != nil { 59 | log.Fatal("can't open config file: ", err) 60 | } 61 | defer file.Close() 62 | decoder := json.NewDecoder(file) 63 | err = decoder.Decode(&config) 64 | if err != nil { 65 | log.Fatal("can't decode config JSON: ", err) 66 | } 67 | var climit = *limit 68 | if climit == 0 { 69 | climit = config.Limit 70 | } 71 | if climit == 0 { 72 | fail("'limit' must be greater than 0") 73 | } 74 | 75 | var cskip = *skip 76 | if cskip == 0 { 77 | cskip = config.Skip 78 | } 79 | 80 | for _, entry := range config.Imports { 81 | var fMongoUri = combine(*mongoUri, entry.MongoUri) 82 | if fMongoUri != "" && entry.MongoDb != "" { 83 | NewExporterFromMongo(fMongoUri, entry.MongoDb, entry.User, ctx).processClient(deviceStatuses, treatments, climit, cskip, ctx) 84 | } 85 | if entry.NsUri != "" && entry.NsToken != "" { 86 | NewExporterFromNS(entry.NsUri, entry.NsToken, entry.User).processClient(deviceStatuses, treatments, climit, cskip, ctx) 87 | } 88 | } 89 | } 90 | 91 | var wgTransform = &sync.WaitGroup{} 92 | wgTransform.Add(2) 93 | 94 | go parseDeviceStatuses(wgTransform, influx, deviceStatuses) 95 | go parseTreatments(wgTransform, influx, treatments) 96 | 97 | go func() { 98 | wgInflux.Add(1) 99 | defer wgInflux.Done() 100 | var count = 0 101 | var fInfluxUri = combineOrFail("InfluxDB uri not supplied", *influxUri, config.InfluxUri) 102 | var fInfluxToken = combineOrFail("InfluxDB token not supplied", *influxToken, config.InfluxToken) 103 | var fInfluxOrg = combineOrFail("InfluxDB token not supplied", *influxOrg, config.InfluxOrg) 104 | var fInfluxBucket = combineOrFail("InfluxDB token not supplied", *influxBucket, config.InfluxBucket) 105 | writeAPI := influxdb2.NewClient(fInfluxUri, fInfluxToken).WriteAPIBlocking(fInfluxOrg, fInfluxBucket) 106 | 107 | for point := range influx { 108 | 109 | if len(point.FieldList()) == 0 && len(point.TagList()) == 0 { 110 | 111 | fmt.Println("empty point for time: ", point.Time(), " of type: ", point.Name()) 112 | continue 113 | } 114 | 115 | err := writeAPI.WritePoint(ctx, &point) 116 | count++ 117 | if err != nil { 118 | fmt.Println("error writing: ", point.Time(), ", name: ", point.Name()) 119 | } 120 | } 121 | 122 | fmt.Println("total writen: ", count) 123 | 124 | }() 125 | 126 | wg.Wait() 127 | close(deviceStatuses) 128 | close(treatments) 129 | wgTransform.Wait() 130 | close(influx) 131 | wgInflux.Wait() 132 | } 133 | 134 | func combineOrFail(message string, values ...string) string { 135 | var result = combine(values...) 136 | if result == "" { 137 | fail(message) 138 | } 139 | return result 140 | } 141 | 142 | func combine(values ...string) string { 143 | var result = "" 144 | for _, value := range values { 145 | if value != "" { 146 | result = value 147 | } 148 | } 149 | return result 150 | } 151 | 152 | func fail(message string) { 153 | fmt.Fprintf(os.Stderr, "error: %v\n", message) 154 | os.Exit(1) 155 | } 156 | 157 | func parseDeviceStatuses(group *sync.WaitGroup, influx chan write.Point, entries chan NsEntry) { 158 | defer group.Done() 159 | 160 | reg := regexp.MustCompile("Dev: (?P[-0-9.]+),.*ISF: (?:(?P[-0-9.]+)/(?P[-0-9.]+)+=)?(?P[-0-9.]+),.*CR: (?P[-0-9.]+)") 161 | 162 | var count = 0 163 | var lastbg = 0.0 164 | var lasttick float64 = 0 165 | 166 | for entry := range entries { 167 | 168 | point := influxdb2.NewPointWithMeasurement("openaps"). 169 | AddField("iob", entry.OpenAps.IOB.IOB). 170 | AddField("basal_iob", entry.OpenAps.IOB.BasalIOB). 171 | AddField("activity", entry.OpenAps.IOB.Activity). 172 | SetTime(entry.OpenAps.IOB.Time) 173 | 174 | if entry.User != "" { 175 | point.AddTag("user", entry.User) 176 | } 177 | 178 | if entry.OpenAps.Suggested.Bg > 0 { 179 | 180 | var tick = entry.OpenAps.Suggested.Tick 181 | if lastbg == entry.OpenAps.Suggested.Bg && 182 | lasttick == tick && 183 | tick != 0.0 { 184 | // deduplication, because nightscout still allows duplicate records to be added 185 | fmt.Println("skipping duplicate bg record: ", entry.OpenAps.IOB.Time, ", bg: ", entry.OpenAps.Suggested.Bg, ", tick: ", tick) 186 | continue 187 | } 188 | 189 | lastbg = entry.OpenAps.Suggested.Bg 190 | lasttick = tick 191 | point. 192 | AddField("bg", entry.OpenAps.Suggested.Bg). 193 | AddField("tick", tick). 194 | AddField("eventual_bg", entry.OpenAps.Suggested.EventualBG). 195 | AddField("target_bg", entry.OpenAps.Suggested.TargetBG). 196 | AddField("insulin_req", entry.OpenAps.Suggested.InsulinReq). 197 | AddField("cob", entry.OpenAps.Suggested.COB). 198 | AddField("bolus", entry.OpenAps.Suggested.Units). 199 | AddField("tbs_rate", entry.OpenAps.Suggested.Rate). 200 | AddField("tbs_duration", entry.OpenAps.Suggested.Duration). 201 | AddField("sens", entry.OpenAps.Suggested.SensitivityRatio) 202 | 203 | if len(entry.OpenAps.Suggested.PredBGs.COB) > 0 { 204 | point.AddField("pred_cob", entry.OpenAps.Suggested.PredBGs.COB[len(entry.OpenAps.Suggested.PredBGs.COB)-1]) 205 | } 206 | if len(entry.OpenAps.Suggested.PredBGs.IOB) > 0 { 207 | point.AddField("pred_iob", entry.OpenAps.Suggested.PredBGs.IOB[len(entry.OpenAps.Suggested.PredBGs.IOB)-1]) 208 | } 209 | if len(entry.OpenAps.Suggested.PredBGs.UAM) > 0 { 210 | point.AddField("pred_uam", entry.OpenAps.Suggested.PredBGs.UAM[len(entry.OpenAps.Suggested.PredBGs.UAM)-1]) 211 | } 212 | if len(entry.OpenAps.Suggested.PredBGs.ZT) > 0 { 213 | point.AddField("pred_zt", entry.OpenAps.Suggested.PredBGs.ZT[len(entry.OpenAps.Suggested.PredBGs.ZT)-1]) 214 | } 215 | if len(entry.OpenAps.Suggested.Reason) > 0 { 216 | matches := reg.FindStringSubmatch(entry.OpenAps.Suggested.Reason) 217 | names := reg.SubexpNames() 218 | for i, match := range matches { 219 | if i != 0 { 220 | if len(match) > 0 { 221 | if rvalue, err := strconv.ParseFloat(match, 32); err == nil { 222 | point.AddField(names[i], rvalue) 223 | } 224 | } 225 | } 226 | } 227 | 228 | point.AddField("reason", html.UnescapeString(entry.OpenAps.Suggested.Reason)) 229 | } 230 | } 231 | 232 | count++ 233 | influx <- *point 234 | 235 | fmt.Println("treatment time+: ", entry.OpenAps.IOB.Time, "iob:", entry.OpenAps.IOB.IOB, ", bg: ", entry.OpenAps.Suggested.Bg) 236 | } 237 | fmt.Println("total devicestatuses parsed: ", count) 238 | } 239 | 240 | func parseTreatments(group *sync.WaitGroup, influx chan write.Point, entries chan NsTreatment) { 241 | defer group.Done() 242 | 243 | var noted = map[string]bool{ 244 | "Site Change": true, 245 | "Insulin Change": true, 246 | "Pump Battery Change": true, 247 | "Sensor Change": true, 248 | "Sensor Start": true, 249 | "Sensor Stop": true, 250 | "BG Check": true, 251 | "Exercise": true, 252 | "Announcement": true, 253 | "Question": true, 254 | //"Note": true, 255 | "OpenAPS Offline": true, 256 | "D.A.D. Alert": true, 257 | "Mbg": true, 258 | //"Carb Correction": true, 259 | //"Bolus Wizard": true, 260 | //"Correction Bolus": true, 261 | //"Meal Bolus": true, 262 | //"Combo Bolus": true, 263 | //"Temporary Target": true, 264 | //"Temporary Target Cancel": true, 265 | "Profile Switch": true, 266 | //"Snack Bolus": true, 267 | //"Temp Basal": true, 268 | //"Temp Basal Start": true, 269 | //"Temp Basal End": true, 270 | } 271 | 272 | var count = 0 273 | for entry := range entries { 274 | 275 | point := influxdb2.NewPointWithMeasurement("treatments"). 276 | SetTime(entry.CreatedAt) 277 | 278 | if entry.User != "" { 279 | point.AddTag("user", entry.User) 280 | } 281 | 282 | tagName := "type" 283 | if entry.Carbs > 0 { 284 | point. 285 | AddField("carbs", entry.Carbs). 286 | AddTag(tagName, "carbs") 287 | } 288 | if entry.Insulin > 0 { 289 | point. 290 | AddField("bolus", entry.Insulin). 291 | AddTag(tagName, "bolus"). 292 | AddTag("smb", strconv.FormatBool(entry.IsSMB)) 293 | } 294 | if entry.EventType == "Temp Basal" { 295 | point. 296 | AddField("duration", entry.Duration). 297 | AddField("percent", entry.Percent). 298 | AddField("rate", entry.Rate). 299 | AddTag(tagName, "tbs") 300 | } else if entry.EventType == "Temporary Target" { 301 | point. 302 | AddField("duration", entry.Duration). 303 | AddField("target_top", entry.TargetTop). 304 | AddField("target_bottom", entry.TargetBottom). 305 | AddField("units", entry.Units). 306 | AddField("reason", entry.Reason). 307 | AddTag(tagName, "tt") 308 | } else if len(entry.Notes) > 0 { 309 | point.AddField("notes", entry.Notes) 310 | } else if noted[entry.EventType] { 311 | point.AddField("notes", entry.EventType) 312 | } 313 | 314 | count++ 315 | influx <- *point 316 | fmt.Println("time: ", point.Time(), ", type: ", entry.EventType) 317 | } 318 | 319 | fmt.Println("total treatments parsed: ", count) 320 | } 321 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= 7 | github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= 8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 9 | github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= 10 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 11 | github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= 12 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 13 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 14 | github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= 15 | github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= 16 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 17 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 18 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 19 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 20 | github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= 21 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= 22 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 23 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 24 | github.com/influxdata/influxdb-client-go/v2 v2.9.0 h1:1Ejxpt+cpWkadefxd5xvVx7pFgFaafdNp1ItfHzKRW4= 25 | github.com/influxdata/influxdb-client-go/v2 v2.9.0/go.mod h1:x7Jo5UHHl+w8wu8UnGiNobDDHygojXwJX4mx7rXGKMk= 26 | github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= 27 | github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= 28 | github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= 29 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 30 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 31 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 32 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 33 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 34 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 35 | github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= 36 | github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= 37 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 38 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 39 | github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= 40 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 41 | github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 42 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 43 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 44 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 45 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 46 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 47 | github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= 48 | github.com/peterbourgon/ff/v3 v3.1.2 h1:0GNhbRhO9yHA4CC27ymskOsuRpmX0YQxwxM9UPiP6JM= 49 | github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE= 50 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 51 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 52 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 56 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 57 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 58 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 59 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 60 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 61 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= 62 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 63 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 64 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 65 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 66 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 67 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 68 | github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= 69 | github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= 70 | github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= 71 | github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= 72 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= 73 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 74 | go.mongodb.org/mongo-driver v1.9.1 h1:m078y9v7sBItkt1aaoe2YlvWEXcD263e1a4E1fBrJ1c= 75 | go.mongodb.org/mongo-driver v1.9.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= 76 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 77 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 78 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 79 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 80 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= 81 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 82 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 83 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 84 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 85 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 86 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 87 | golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM= 88 | golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 89 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 90 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 91 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 92 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 93 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 94 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 95 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 96 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 97 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 98 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 99 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 100 | golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 101 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 102 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 103 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 105 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 106 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 107 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 108 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 109 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 110 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 111 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 112 | golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 113 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 114 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 115 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 116 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 117 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 118 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 119 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 120 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 121 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 122 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 123 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 124 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 125 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 126 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 127 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 128 | -------------------------------------------------------------------------------- /grafana.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_INFLUXDB", 5 | "label": "InfluxDB", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "influxdb", 9 | "pluginName": "InfluxDB" 10 | }, 11 | { 12 | "name": "DS_INFLUXDB_QL", 13 | "label": "InfluxDB QL", 14 | "description": "", 15 | "type": "datasource", 16 | "pluginId": "influxdb", 17 | "pluginName": "InfluxDB" 18 | } 19 | ], 20 | "__elements": [], 21 | "__requires": [ 22 | { 23 | "type": "grafana", 24 | "id": "grafana", 25 | "name": "Grafana", 26 | "version": "9.0.0" 27 | }, 28 | { 29 | "type": "datasource", 30 | "id": "influxdb", 31 | "name": "InfluxDB", 32 | "version": "1.0.0" 33 | }, 34 | { 35 | "type": "panel", 36 | "id": "piechart", 37 | "name": "Pie chart", 38 | "version": "" 39 | }, 40 | { 41 | "type": "panel", 42 | "id": "stat", 43 | "name": "Stat", 44 | "version": "" 45 | }, 46 | { 47 | "type": "panel", 48 | "id": "table", 49 | "name": "Table", 50 | "version": "" 51 | }, 52 | { 53 | "type": "panel", 54 | "id": "timeseries", 55 | "name": "Time series", 56 | "version": "" 57 | } 58 | ], 59 | "annotations": { 60 | "list": [ 61 | { 62 | "datasource": { 63 | "type": "influxdb", 64 | "uid": "${DS_INFLUXDB}" 65 | }, 66 | "enable": false, 67 | "iconColor": "#ff983047", 68 | "mappings": { 69 | "text": { 70 | "source": "field", 71 | "value": "notes carbs" 72 | } 73 | }, 74 | "name": " carbs & notes", 75 | "target": { 76 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"treatments\")\n |> filter(fn: (r) => r[\"_field\"] == \"notes\")", 77 | "refId": "Anno" 78 | } 79 | }, 80 | { 81 | "builtIn": 1, 82 | "datasource": { 83 | "type": "grafana", 84 | "uid": "-- Grafana --" 85 | }, 86 | "enable": true, 87 | "hide": true, 88 | "iconColor": "rgba(0, 211, 255, 1)", 89 | "name": "Annotations & Alerts", 90 | "target": { 91 | "limit": 100, 92 | "matchAny": false, 93 | "tags": [], 94 | "type": "dashboard" 95 | }, 96 | "type": "dashboard" 97 | } 98 | ] 99 | }, 100 | "editable": true, 101 | "fiscalYearStartMonth": 0, 102 | "graphTooltip": 2, 103 | "id": null, 104 | "links": [], 105 | "liveNow": false, 106 | "panels": [ 107 | { 108 | "datasource": { 109 | "type": "influxdb", 110 | "uid": "${DS_INFLUXDB}" 111 | }, 112 | "fieldConfig": { 113 | "defaults": { 114 | "color": { 115 | "mode": "thresholds" 116 | }, 117 | "decimals": 1, 118 | "mappings": [], 119 | "min": 0, 120 | "thresholds": { 121 | "mode": "absolute", 122 | "steps": [ 123 | { 124 | "color": "dark-red", 125 | "value": null 126 | }, 127 | { 128 | "color": "green", 129 | "value": 4 130 | }, 131 | { 132 | "color": "#EAB839", 133 | "value": 9 134 | } 135 | ] 136 | } 137 | }, 138 | "overrides": [] 139 | }, 140 | "gridPos": { 141 | "h": 5, 142 | "w": 3, 143 | "x": 0, 144 | "y": 0 145 | }, 146 | "id": 17, 147 | "options": { 148 | "colorMode": "value", 149 | "graphMode": "area", 150 | "justifyMode": "auto", 151 | "orientation": "auto", 152 | "reduceOptions": { 153 | "calcs": [ 154 | "last" 155 | ], 156 | "fields": "/^bg$/", 157 | "values": false 158 | }, 159 | "textMode": "auto" 160 | }, 161 | "pluginVersion": "9.0.0", 162 | "targets": [ 163 | { 164 | "datasource": { 165 | "type": "influxdb", 166 | "uid": "${DS_INFLUXDB}" 167 | }, 168 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r[\"_field\"] == \"bg\" )\n |> aggregateWindow(every: 1m, fn: last, createEmpty: false)\n |> map(fn: (r) => ({ r with \n _value: r._value/18.0,\n _measurement: \"\"\n }))", 169 | "refId": "A" 170 | } 171 | ], 172 | "timeFrom": "3h", 173 | "title": "current", 174 | "type": "stat" 175 | }, 176 | { 177 | "datasource": { 178 | "type": "datasource", 179 | "uid": "-- Mixed --" 180 | }, 181 | "fieldConfig": { 182 | "defaults": { 183 | "color": { 184 | "mode": "fixed" 185 | }, 186 | "custom": { 187 | "axisLabel": "", 188 | "axisPlacement": "left", 189 | "axisSoftMax": 14, 190 | "barAlignment": 0, 191 | "drawStyle": "line", 192 | "fillOpacity": 0, 193 | "gradientMode": "scheme", 194 | "hideFrom": { 195 | "legend": false, 196 | "tooltip": false, 197 | "viz": false 198 | }, 199 | "lineInterpolation": "linear", 200 | "lineWidth": 1, 201 | "pointSize": 5, 202 | "scaleDistribution": { 203 | "type": "linear" 204 | }, 205 | "showPoints": "never", 206 | "spanNulls": false, 207 | "stacking": { 208 | "group": "A", 209 | "mode": "none" 210 | }, 211 | "thresholdsStyle": { 212 | "mode": "off" 213 | } 214 | }, 215 | "mappings": [], 216 | "thresholds": { 217 | "mode": "absolute", 218 | "steps": [ 219 | { 220 | "color": "dark-red", 221 | "value": null 222 | }, 223 | { 224 | "color": "green", 225 | "value": 3.9 226 | }, 227 | { 228 | "color": "#EAB839", 229 | "value": 10 230 | } 231 | ] 232 | } 233 | }, 234 | "overrides": [ 235 | { 236 | "matcher": { 237 | "id": "byName", 238 | "options": "iob" 239 | }, 240 | "properties": [ 241 | { 242 | "id": "custom.gradientMode", 243 | "value": "none" 244 | }, 245 | { 246 | "id": "color", 247 | "value": { 248 | "fixedColor": "blue", 249 | "mode": "fixed" 250 | } 251 | }, 252 | { 253 | "id": "custom.lineInterpolation", 254 | "value": "smooth" 255 | }, 256 | { 257 | "id": "custom.showPoints", 258 | "value": "never" 259 | }, 260 | { 261 | "id": "min", 262 | "value": 0 263 | }, 264 | { 265 | "id": "custom.axisPlacement", 266 | "value": "hidden" 267 | } 268 | ] 269 | }, 270 | { 271 | "matcher": { 272 | "id": "byName", 273 | "options": "bolus" 274 | }, 275 | "properties": [ 276 | { 277 | "id": "custom.drawStyle", 278 | "value": "bars" 279 | }, 280 | { 281 | "id": "color", 282 | "value": { 283 | "fixedColor": "dark-blue", 284 | "mode": "fixed" 285 | } 286 | }, 287 | { 288 | "id": "custom.fillOpacity", 289 | "value": 100 290 | }, 291 | { 292 | "id": "custom.axisPlacement", 293 | "value": "right" 294 | }, 295 | { 296 | "id": "min", 297 | "value": 0 298 | }, 299 | { 300 | "id": "max", 301 | "value": 1 302 | } 303 | ] 304 | }, 305 | { 306 | "matcher": { 307 | "id": "byName", 308 | "options": "bolus smb" 309 | }, 310 | "properties": [ 311 | { 312 | "id": "custom.drawStyle", 313 | "value": "bars" 314 | }, 315 | { 316 | "id": "color", 317 | "value": { 318 | "fixedColor": "#184992", 319 | "mode": "fixed" 320 | } 321 | }, 322 | { 323 | "id": "custom.fillOpacity", 324 | "value": 100 325 | }, 326 | { 327 | "id": "custom.axisPlacement", 328 | "value": "right" 329 | }, 330 | { 331 | "id": "min", 332 | "value": 0 333 | }, 334 | { 335 | "id": "max", 336 | "value": 1 337 | }, 338 | { 339 | "id": "decimals", 340 | "value": 1 341 | }, 342 | { 343 | "id": "custom.hideFrom", 344 | "value": { 345 | "legend": false, 346 | "tooltip": false, 347 | "viz": false 348 | } 349 | } 350 | ] 351 | }, 352 | { 353 | "matcher": { 354 | "id": "byName", 355 | "options": "insulin_req" 356 | }, 357 | "properties": [ 358 | { 359 | "id": "custom.showPoints", 360 | "value": "never" 361 | }, 362 | { 363 | "id": "color", 364 | "value": { 365 | "fixedColor": "light-blue", 366 | "mode": "fixed" 367 | } 368 | } 369 | ] 370 | }, 371 | { 372 | "matcher": { 373 | "id": "byName", 374 | "options": "bg" 375 | }, 376 | "properties": [ 377 | { 378 | "id": "custom.fillOpacity", 379 | "value": 0 380 | }, 381 | { 382 | "id": "custom.lineWidth", 383 | "value": 4 384 | }, 385 | { 386 | "id": "color", 387 | "value": { 388 | "mode": "thresholds" 389 | } 390 | }, 391 | { 392 | "id": "custom.thresholdsStyle", 393 | "value": { 394 | "mode": "line" 395 | } 396 | }, 397 | { 398 | "id": "custom.drawStyle", 399 | "value": "line" 400 | }, 401 | { 402 | "id": "custom.showPoints", 403 | "value": "never" 404 | }, 405 | { 406 | "id": "custom.pointSize", 407 | "value": 10 408 | }, 409 | { 410 | "id": "custom.lineInterpolation", 411 | "value": "smooth" 412 | }, 413 | { 414 | "id": "custom.axisPlacement", 415 | "value": "left" 416 | }, 417 | { 418 | "id": "custom.hideFrom", 419 | "value": { 420 | "legend": false, 421 | "tooltip": false, 422 | "viz": false 423 | } 424 | }, 425 | { 426 | "id": "min", 427 | "value": 2 428 | }, 429 | { 430 | "id": "custom.axisPlacement", 431 | "value": "left" 432 | } 433 | ] 434 | }, 435 | { 436 | "matcher": { 437 | "id": "byName", 438 | "options": "cob" 439 | }, 440 | "properties": [ 441 | { 442 | "id": "custom.axisPlacement", 443 | "value": "right" 444 | }, 445 | { 446 | "id": "color", 447 | "value": { 448 | "fixedColor": "orange", 449 | "mode": "fixed" 450 | } 451 | }, 452 | { 453 | "id": "custom.fillOpacity", 454 | "value": 16 455 | }, 456 | { 457 | "id": "custom.showPoints", 458 | "value": "never" 459 | } 460 | ] 461 | }, 462 | { 463 | "matcher": { 464 | "id": "byName", 465 | "options": "isf" 466 | }, 467 | "properties": [ 468 | { 469 | "id": "custom.lineStyle", 470 | "value": { 471 | "dash": [ 472 | 0, 473 | 10 474 | ], 475 | "fill": "dot" 476 | } 477 | }, 478 | { 479 | "id": "color", 480 | "value": { 481 | "fixedColor": "light-blue", 482 | "mode": "fixed" 483 | } 484 | }, 485 | { 486 | "id": "custom.showPoints", 487 | "value": "never" 488 | }, 489 | { 490 | "id": "custom.lineInterpolation", 491 | "value": "stepAfter" 492 | }, 493 | { 494 | "id": "custom.hideFrom", 495 | "value": { 496 | "legend": false, 497 | "tooltip": false, 498 | "viz": false 499 | } 500 | }, 501 | { 502 | "id": "custom.axisPlacement", 503 | "value": "hidden" 504 | }, 505 | { 506 | "id": "min", 507 | "value": 2 508 | } 509 | ] 510 | }, 511 | { 512 | "matcher": { 513 | "id": "byName", 514 | "options": "target_bg" 515 | }, 516 | "properties": [ 517 | { 518 | "id": "custom.lineInterpolation", 519 | "value": "stepAfter" 520 | }, 521 | { 522 | "id": "custom.showPoints", 523 | "value": "never" 524 | }, 525 | { 526 | "id": "color", 527 | "value": { 528 | "fixedColor": "dark-green", 529 | "mode": "fixed" 530 | } 531 | }, 532 | { 533 | "id": "custom.lineStyle", 534 | "value": { 535 | "dash": [ 536 | 10, 537 | 10 538 | ], 539 | "fill": "dash" 540 | } 541 | }, 542 | { 543 | "id": "custom.axisPlacement", 544 | "value": "hidden" 545 | }, 546 | { 547 | "id": "min", 548 | "value": 2 549 | } 550 | ] 551 | }, 552 | { 553 | "matcher": { 554 | "id": "byName", 555 | "options": "activity" 556 | }, 557 | "properties": [ 558 | { 559 | "id": "custom.lineStyle", 560 | "value": { 561 | "dash": [ 562 | 0, 563 | 10 564 | ], 565 | "fill": "dot" 566 | } 567 | }, 568 | { 569 | "id": "color", 570 | "value": { 571 | "fixedColor": "light-yellow", 572 | "mode": "fixed" 573 | } 574 | }, 575 | { 576 | "id": "max", 577 | "value": 17 578 | }, 579 | { 580 | "id": "custom.axisPlacement", 581 | "value": "hidden" 582 | }, 583 | { 584 | "id": "custom.hideFrom", 585 | "value": { 586 | "legend": false, 587 | "tooltip": true, 588 | "viz": false 589 | } 590 | }, 591 | { 592 | "id": "custom.showPoints", 593 | "value": "never" 594 | } 595 | ] 596 | }, 597 | { 598 | "matcher": { 599 | "id": "byName", 600 | "options": "basal" 601 | }, 602 | "properties": [ 603 | { 604 | "id": "max", 605 | "value": 0 606 | }, 607 | { 608 | "id": "custom.axisPlacement", 609 | "value": "hidden" 610 | }, 611 | { 612 | "id": "min", 613 | "value": -2000 614 | }, 615 | { 616 | "id": "custom.axisSoftMin", 617 | "value": -1500 618 | }, 619 | { 620 | "id": "custom.lineInterpolation", 621 | "value": "stepAfter" 622 | }, 623 | { 624 | "id": "color", 625 | "value": { 626 | "fixedColor": "#233e67d4", 627 | "mode": "fixed" 628 | } 629 | }, 630 | { 631 | "id": "custom.fillOpacity", 632 | "value": 70 633 | } 634 | ] 635 | } 636 | ] 637 | }, 638 | "gridPos": { 639 | "h": 11, 640 | "w": 15, 641 | "x": 3, 642 | "y": 0 643 | }, 644 | "id": 15, 645 | "maxDataPoints": 1000, 646 | "options": { 647 | "legend": { 648 | "calcs": [ 649 | "mean", 650 | "sum" 651 | ], 652 | "displayMode": "table", 653 | "placement": "right" 654 | }, 655 | "tooltip": { 656 | "mode": "multi", 657 | "sort": "none" 658 | } 659 | }, 660 | "targets": [ 661 | { 662 | "datasource": { 663 | "type": "influxdb", 664 | "uid": "${DS_INFLUXDB}" 665 | }, 666 | "hide": false, 667 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r._value > 0)\n |> filter(fn: (r) => r[\"_field\"] == \"bg\" \n or r[\"_field\"] == \"iob\"\n or r[\"_field\"] == \"bg\" \n or r[\"_field\"] == \"isf\" \n or r[\"_field\"] == \"target_bg\"\n or r[\"_field\"] == \"activity\")\n |> aggregateWindow(every: 1m, fn: last, createEmpty: false)\n |> map(fn: (r) => ({ r with \n _value: if r._field == \"bg\" or r._field == \"target_bg\" then r._value/18.0 else if r._field == \"activity\" then r._value*500.0 else r._value,\n _measurement: \"\"\n }))", 668 | "refId": "A" 669 | }, 670 | { 671 | "datasource": { 672 | "type": "influxdb", 673 | "uid": "${DS_INFLUXDB}" 674 | }, 675 | "hide": false, 676 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"treatments\")\n |> filter(fn: (r) => r[\"type\"] == \"bolus\")\n |> filter(fn: (r) => r[\"_field\"] == \"bolus\")\n |> map(fn: (r) => ({ r with \n _measurement: \"\",\n _field: if r.smb == \"true\" then \"bolus smb\" else r._field, \n }))\n |> drop(columns: [\"type\", \"smb\"])", 677 | "refId": "B" 678 | }, 679 | { 680 | "datasource": { 681 | "type": "influxdb", 682 | "uid": "${DS_INFLUXDB}" 683 | }, 684 | "hide": false, 685 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"treatments\")\n |> filter(fn: (r) => r[\"type\"] == \"tbs\" and r[\"_field\"] == \"percent\")\n |> map(fn: (r) => ({ _time: r._time, \"basal\": (r._value + 100)*(-1)}))", 686 | "refId": "C" 687 | } 688 | ], 689 | "title": "BG", 690 | "type": "timeseries" 691 | }, 692 | { 693 | "datasource": { 694 | "type": "influxdb", 695 | "uid": "${DS_INFLUXDB}" 696 | }, 697 | "fieldConfig": { 698 | "defaults": { 699 | "custom": { 700 | "align": "auto", 701 | "displayMode": "color-text", 702 | "filterable": false, 703 | "inspect": false 704 | }, 705 | "mappings": [], 706 | "thresholds": { 707 | "mode": "absolute", 708 | "steps": [ 709 | { 710 | "color": "green", 711 | "value": null 712 | }, 713 | { 714 | "color": "red", 715 | "value": 80 716 | } 717 | ] 718 | } 719 | }, 720 | "overrides": [ 721 | { 722 | "matcher": { 723 | "id": "byName", 724 | "options": "carbs" 725 | }, 726 | "properties": [ 727 | { 728 | "id": "custom.width", 729 | "value": 55 730 | } 731 | ] 732 | }, 733 | { 734 | "matcher": { 735 | "id": "byName", 736 | "options": "time" 737 | }, 738 | "properties": [ 739 | { 740 | "id": "unit", 741 | "value": "dateTimeFromNow" 742 | }, 743 | { 744 | "id": "custom.width", 745 | "value": 119 746 | } 747 | ] 748 | } 749 | ] 750 | }, 751 | "gridPos": { 752 | "h": 24, 753 | "w": 4, 754 | "x": 18, 755 | "y": 0 756 | }, 757 | "id": 19, 758 | "options": { 759 | "footer": { 760 | "fields": "", 761 | "reducer": [ 762 | "sum" 763 | ], 764 | "show": false 765 | }, 766 | "showHeader": true, 767 | "sortBy": [] 768 | }, 769 | "pluginVersion": "9.0.0", 770 | "targets": [ 771 | { 772 | "datasource": { 773 | "type": "influxdb", 774 | "uid": "${DS_INFLUXDB}" 775 | }, 776 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"treatments\")\n |> filter(fn: (r) => r[\"_field\"] == \"carbs\" or r[\"_field\"] == \"notes\")\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\n |> sort(columns: [\"_time\"], desc: true)\n |> group()\n |> sort(columns: [\"_time\"], desc: true)\n |> map(fn: (r) => ({ time: time(v:r._time), carbs: r.carbs, notes: r.notes }))", 777 | "refId": "A" 778 | } 779 | ], 780 | "title": "food & notes", 781 | "type": "table" 782 | }, 783 | { 784 | "datasource": { 785 | "type": "influxdb", 786 | "uid": "${DS_INFLUXDB}" 787 | }, 788 | "fieldConfig": { 789 | "defaults": { 790 | "color": { 791 | "mode": "thresholds" 792 | }, 793 | "decimals": 1, 794 | "mappings": [ 795 | { 796 | "options": { 797 | "from": 0.11, 798 | "result": { 799 | "index": 0, 800 | "text": "🛫" 801 | }, 802 | "to": 0.5 803 | }, 804 | "type": "range" 805 | }, 806 | { 807 | "options": { 808 | "from": -0.5, 809 | "result": { 810 | "index": 1, 811 | "text": "🛬" 812 | }, 813 | "to": -0.05 814 | }, 815 | "type": "range" 816 | }, 817 | { 818 | "options": { 819 | "from": -0.1, 820 | "result": { 821 | "index": 2, 822 | "text": "🐌" 823 | }, 824 | "to": 0.1 825 | }, 826 | "type": "range" 827 | }, 828 | { 829 | "options": { 830 | "from": -100, 831 | "result": { 832 | "index": 3, 833 | "text": "⬇" 834 | }, 835 | "to": -0.5 836 | }, 837 | "type": "range" 838 | }, 839 | { 840 | "options": { 841 | "from": 0.51, 842 | "result": { 843 | "index": 4, 844 | "text": "🚀" 845 | }, 846 | "to": 10 847 | }, 848 | "type": "range" 849 | } 850 | ], 851 | "min": 0, 852 | "thresholds": { 853 | "mode": "absolute", 854 | "steps": [ 855 | { 856 | "color": "dark-red", 857 | "value": null 858 | }, 859 | { 860 | "color": "green", 861 | "value": 4 862 | }, 863 | { 864 | "color": "#EAB839", 865 | "value": 9 866 | } 867 | ] 868 | } 869 | }, 870 | "overrides": [] 871 | }, 872 | "gridPos": { 873 | "h": 4, 874 | "w": 3, 875 | "x": 0, 876 | "y": 5 877 | }, 878 | "id": 22, 879 | "options": { 880 | "colorMode": "value", 881 | "graphMode": "none", 882 | "justifyMode": "center", 883 | "orientation": "auto", 884 | "reduceOptions": { 885 | "calcs": [ 886 | "last" 887 | ], 888 | "fields": "/^value$/", 889 | "values": false 890 | }, 891 | "text": {}, 892 | "textMode": "auto" 893 | }, 894 | "pluginVersion": "9.0.0", 895 | "targets": [ 896 | { 897 | "datasource": { 898 | "type": "influxdb", 899 | "uid": "${DS_INFLUXDB}" 900 | }, 901 | "query": "from(bucket: \"ns\")\n |> range(start: -15m, stop: now())\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r[\"_field\"] == \"bg\")\n |> limit(n: 2)\n |> derivative(unit: 5m)\n |> map(fn: (r) => ({ value: r._value / 18.0}))", 902 | "refId": "A" 903 | } 904 | ], 905 | "title": "trend", 906 | "type": "stat" 907 | }, 908 | { 909 | "datasource": { 910 | "type": "influxdb", 911 | "uid": "${DS_INFLUXDB}" 912 | }, 913 | "fieldConfig": { 914 | "defaults": { 915 | "color": { 916 | "mode": "thresholds" 917 | }, 918 | "custom": { 919 | "align": "left", 920 | "displayMode": "color-text", 921 | "inspect": false, 922 | "width": 85 923 | }, 924 | "decimals": 1, 925 | "mappings": [], 926 | "min": 0, 927 | "thresholds": { 928 | "mode": "absolute", 929 | "steps": [ 930 | { 931 | "color": "dark-red", 932 | "value": null 933 | }, 934 | { 935 | "color": "semi-dark-yellow", 936 | "value": -0.5 937 | }, 938 | { 939 | "color": "green", 940 | "value": 0 941 | }, 942 | { 943 | "color": "#EAB839", 944 | "value": 0.5 945 | }, 946 | { 947 | "color": "semi-dark-red", 948 | "value": 1 949 | } 950 | ] 951 | } 952 | }, 953 | "overrides": [] 954 | }, 955 | "gridPos": { 956 | "h": 3, 957 | "w": 3, 958 | "x": 0, 959 | "y": 9 960 | }, 961 | "id": 23, 962 | "options": { 963 | "footer": { 964 | "fields": "", 965 | "reducer": [ 966 | "sum" 967 | ], 968 | "show": false 969 | }, 970 | "showHeader": false 971 | }, 972 | "pluginVersion": "9.0.0", 973 | "targets": [ 974 | { 975 | "datasource": { 976 | "type": "influxdb", 977 | "uid": "${DS_INFLUXDB}" 978 | }, 979 | "query": "import \"experimental/aggregate\"\n\nt1 = from(bucket: \"ns\")\n |> range(start: -15m, stop: now())\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r[\"_field\"] == \"bg\")\n |> limit(n: 2)\n |> derivative(unit: 5m)\n |> map(fn: (r) => ({ index: 0, name: \"Ⲇ 5m\", value: r._value / 18.0}))\n\nt2 = from(bucket: \"ns\")\n |> range(start: -30m, stop: now())\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r[\"_field\"] == \"bg\")\n |> limit(n: 3)\n |> derivative(unit: 5m)\n |> movingAverage(n: 3)\n |> map(fn: (r) => ({ index: 1, name: \"Ⲇ 15m\", value: r._value / 18.0}))\n\nt3 = from(bucket: \"ns\")\n |> range(start: -45m, stop: now())\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r[\"_field\"] == \"bg\")\n |> limit(n: 8)\n |> derivative(unit: 5m)\n |> movingAverage(n: 8)\n |> map(fn: (r) => ({ index: 2, name: \"Ⲇ 40m\", value: r._value / 18.0}))\n\nunion(tables: [t3, t2, t1])\n |> sort(columns: [\"index\"])\n |> drop(columns: [\"index\"])", 980 | "refId": "A" 981 | } 982 | ], 983 | "type": "table" 984 | }, 985 | { 986 | "datasource": { 987 | "type": "datasource", 988 | "uid": "-- Mixed --" 989 | }, 990 | "fieldConfig": { 991 | "defaults": { 992 | "color": { 993 | "mode": "fixed" 994 | }, 995 | "custom": { 996 | "axisLabel": "", 997 | "axisPlacement": "auto", 998 | "axisWidth": 5, 999 | "barAlignment": 0, 1000 | "drawStyle": "line", 1001 | "fillOpacity": 0, 1002 | "gradientMode": "none", 1003 | "hideFrom": { 1004 | "legend": false, 1005 | "tooltip": false, 1006 | "viz": false 1007 | }, 1008 | "lineInterpolation": "linear", 1009 | "lineWidth": 1, 1010 | "pointSize": 5, 1011 | "scaleDistribution": { 1012 | "type": "linear" 1013 | }, 1014 | "showPoints": "never", 1015 | "spanNulls": false, 1016 | "stacking": { 1017 | "group": "A", 1018 | "mode": "none" 1019 | }, 1020 | "thresholdsStyle": { 1021 | "mode": "off" 1022 | } 1023 | }, 1024 | "mappings": [], 1025 | "min": 2.18, 1026 | "thresholds": { 1027 | "mode": "absolute", 1028 | "steps": [ 1029 | { 1030 | "color": "dark-red", 1031 | "value": null 1032 | } 1033 | ] 1034 | } 1035 | }, 1036 | "overrides": [ 1037 | { 1038 | "matcher": { 1039 | "id": "byName", 1040 | "options": "pred_iob" 1041 | }, 1042 | "properties": [ 1043 | { 1044 | "id": "color", 1045 | "value": { 1046 | "fixedColor": "light-blue", 1047 | "mode": "fixed" 1048 | } 1049 | } 1050 | ] 1051 | }, 1052 | { 1053 | "matcher": { 1054 | "id": "byName", 1055 | "options": "pred_cob" 1056 | }, 1057 | "properties": [ 1058 | { 1059 | "id": "color", 1060 | "value": { 1061 | "fixedColor": "orange", 1062 | "mode": "fixed" 1063 | } 1064 | } 1065 | ] 1066 | }, 1067 | { 1068 | "matcher": { 1069 | "id": "byName", 1070 | "options": "pred_uam" 1071 | }, 1072 | "properties": [ 1073 | { 1074 | "id": "color", 1075 | "value": { 1076 | "fixedColor": "light-yellow", 1077 | "mode": "fixed" 1078 | } 1079 | } 1080 | ] 1081 | } 1082 | ] 1083 | }, 1084 | "gridPos": { 1085 | "h": 5, 1086 | "w": 12, 1087 | "x": 3, 1088 | "y": 11 1089 | }, 1090 | "id": 6, 1091 | "interval": "5m", 1092 | "maxDataPoints": 1000, 1093 | "options": { 1094 | "legend": { 1095 | "calcs": [], 1096 | "displayMode": "list", 1097 | "placement": "bottom" 1098 | }, 1099 | "tooltip": { 1100 | "mode": "multi", 1101 | "sort": "none" 1102 | } 1103 | }, 1104 | "targets": [ 1105 | { 1106 | "datasource": { 1107 | "type": "influxdb", 1108 | "uid": "${DS_INFLUXDB}" 1109 | }, 1110 | "hide": false, 1111 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r[\"_field\"] == \"pred_zt\" or r[\"_field\"] == \"pred_uam\" or r[\"_field\"] == \"pred_iob\" or r[\"_field\"] == \"pred_cob\")\n |> aggregateWindow(every: 5m, fn: last, createEmpty: true)\n |> fill(value: 0.0)\n |> map(fn: (r) => ({ r with _value: float(v: r._value)/18.0, _measurement: \"\" }))", 1112 | "refId": "D" 1113 | } 1114 | ], 1115 | "title": "Predictions", 1116 | "transformations": [ 1117 | { 1118 | "id": "filterByValue", 1119 | "options": { 1120 | "filters": [ 1121 | { 1122 | "config": { 1123 | "id": "isNull", 1124 | "options": {} 1125 | }, 1126 | "fieldName": "pred_cob" 1127 | } 1128 | ], 1129 | "match": "all", 1130 | "type": "exclude" 1131 | } 1132 | } 1133 | ], 1134 | "type": "timeseries" 1135 | }, 1136 | { 1137 | "datasource": { 1138 | "type": "influxdb", 1139 | "uid": "${DS_INFLUXDB}" 1140 | }, 1141 | "fieldConfig": { 1142 | "defaults": { 1143 | "color": { 1144 | "mode": "palette-classic" 1145 | }, 1146 | "custom": { 1147 | "hideFrom": { 1148 | "legend": false, 1149 | "tooltip": false, 1150 | "viz": false 1151 | } 1152 | }, 1153 | "mappings": [] 1154 | }, 1155 | "overrides": [ 1156 | { 1157 | "matcher": { 1158 | "id": "byName", 1159 | "options": "normal" 1160 | }, 1161 | "properties": [ 1162 | { 1163 | "id": "color", 1164 | "value": { 1165 | "fixedColor": "green", 1166 | "mode": "fixed" 1167 | } 1168 | } 1169 | ] 1170 | }, 1171 | { 1172 | "matcher": { 1173 | "id": "byName", 1174 | "options": "high" 1175 | }, 1176 | "properties": [ 1177 | { 1178 | "id": "color", 1179 | "value": { 1180 | "fixedColor": "yellow", 1181 | "mode": "fixed" 1182 | } 1183 | } 1184 | ] 1185 | }, 1186 | { 1187 | "matcher": { 1188 | "id": "byName", 1189 | "options": "low" 1190 | }, 1191 | "properties": [ 1192 | { 1193 | "id": "color", 1194 | "value": { 1195 | "fixedColor": "semi-dark-red", 1196 | "mode": "fixed" 1197 | } 1198 | } 1199 | ] 1200 | } 1201 | ] 1202 | }, 1203 | "gridPos": { 1204 | "h": 10, 1205 | "w": 3, 1206 | "x": 0, 1207 | "y": 12 1208 | }, 1209 | "id": 10, 1210 | "options": { 1211 | "displayLabels": [], 1212 | "legend": { 1213 | "displayMode": "table", 1214 | "placement": "bottom", 1215 | "values": [ 1216 | "percent" 1217 | ] 1218 | }, 1219 | "pieType": "pie", 1220 | "reduceOptions": { 1221 | "calcs": [ 1222 | "lastNotNull" 1223 | ], 1224 | "fields": "/^_value$/", 1225 | "values": true 1226 | }, 1227 | "tooltip": { 1228 | "mode": "none", 1229 | "sort": "none" 1230 | } 1231 | }, 1232 | "targets": [ 1233 | { 1234 | "datasource": { 1235 | "type": "influxdb", 1236 | "uid": "${DS_INFLUXDB}" 1237 | }, 1238 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r[\"_field\"] == \"bg\")\n |> keep(columns: [\"_value\"])\n |> map(fn: (r) => ({_value: r._value, label: if r._value < 70 then \"low\" else if r._value < 180 then \"normal\" else \"high\" }))\n |> group(columns: [\"label\"])\n |> count()\n |> group()", 1239 | "refId": "A" 1240 | } 1241 | ], 1242 | "title": "time in range", 1243 | "type": "piechart" 1244 | }, 1245 | { 1246 | "datasource": { 1247 | "type": "influxdb", 1248 | "uid": "${DS_INFLUXDB}" 1249 | }, 1250 | "fieldConfig": { 1251 | "defaults": { 1252 | "color": { 1253 | "fixedColor": "orange", 1254 | "mode": "fixed" 1255 | }, 1256 | "custom": { 1257 | "axisLabel": "", 1258 | "axisPlacement": "hidden", 1259 | "axisSoftMin": 0, 1260 | "barAlignment": 0, 1261 | "drawStyle": "line", 1262 | "fillOpacity": 25, 1263 | "gradientMode": "scheme", 1264 | "hideFrom": { 1265 | "legend": false, 1266 | "tooltip": false, 1267 | "viz": false 1268 | }, 1269 | "lineInterpolation": "linear", 1270 | "lineWidth": 1, 1271 | "pointSize": 5, 1272 | "scaleDistribution": { 1273 | "type": "linear" 1274 | }, 1275 | "showPoints": "never", 1276 | "spanNulls": false, 1277 | "stacking": { 1278 | "group": "A", 1279 | "mode": "none" 1280 | }, 1281 | "thresholdsStyle": { 1282 | "mode": "off" 1283 | } 1284 | }, 1285 | "mappings": [], 1286 | "thresholds": { 1287 | "mode": "absolute", 1288 | "steps": [ 1289 | { 1290 | "color": "green", 1291 | "value": null 1292 | }, 1293 | { 1294 | "color": "dark-red", 1295 | "value": 0 1296 | }, 1297 | { 1298 | "color": "green", 1299 | "value": 3.9 1300 | }, 1301 | { 1302 | "color": "#EAB839", 1303 | "value": 10 1304 | } 1305 | ] 1306 | } 1307 | }, 1308 | "overrides": [ 1309 | { 1310 | "matcher": { 1311 | "id": "byName", 1312 | "options": "cr" 1313 | }, 1314 | "properties": [ 1315 | { 1316 | "id": "custom.lineStyle", 1317 | "value": { 1318 | "dash": [ 1319 | 10, 1320 | 10 1321 | ], 1322 | "fill": "dash" 1323 | } 1324 | }, 1325 | { 1326 | "id": "custom.fillOpacity", 1327 | "value": 0 1328 | } 1329 | ] 1330 | } 1331 | ] 1332 | }, 1333 | "gridPos": { 1334 | "h": 5, 1335 | "w": 12, 1336 | "x": 3, 1337 | "y": 16 1338 | }, 1339 | "id": 8, 1340 | "options": { 1341 | "legend": { 1342 | "calcs": [], 1343 | "displayMode": "hidden", 1344 | "placement": "bottom" 1345 | }, 1346 | "tooltip": { 1347 | "mode": "multi", 1348 | "sort": "none" 1349 | } 1350 | }, 1351 | "targets": [ 1352 | { 1353 | "datasource": { 1354 | "type": "influxdb", 1355 | "uid": "${DS_INFLUXDB}" 1356 | }, 1357 | "hide": false, 1358 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r[\"_field\"] == \"cob\" or r[\"_field\"] == \"cr\")\n |> aggregateWindow(every: 1m, fn: last, createEmpty: false)\n |> map(fn: (r) => ({ r with _measurement: \"\" }))", 1359 | "refId": "A" 1360 | } 1361 | ], 1362 | "title": "cob", 1363 | "type": "timeseries" 1364 | }, 1365 | { 1366 | "datasource": { 1367 | "type": "influxdb", 1368 | "uid": "${DS_INFLUXDB}" 1369 | }, 1370 | "fieldConfig": { 1371 | "defaults": { 1372 | "color": { 1373 | "mode": "thresholds" 1374 | }, 1375 | "custom": { 1376 | "axisLabel": "", 1377 | "axisPlacement": "hidden", 1378 | "axisSoftMin": 0, 1379 | "barAlignment": 0, 1380 | "drawStyle": "bars", 1381 | "fillOpacity": 100, 1382 | "gradientMode": "scheme", 1383 | "hideFrom": { 1384 | "legend": false, 1385 | "tooltip": false, 1386 | "viz": false 1387 | }, 1388 | "lineInterpolation": "linear", 1389 | "lineWidth": 1, 1390 | "pointSize": 5, 1391 | "scaleDistribution": { 1392 | "type": "linear" 1393 | }, 1394 | "showPoints": "never", 1395 | "spanNulls": false, 1396 | "stacking": { 1397 | "group": "A", 1398 | "mode": "none" 1399 | }, 1400 | "thresholdsStyle": { 1401 | "mode": "off" 1402 | } 1403 | }, 1404 | "mappings": [], 1405 | "thresholds": { 1406 | "mode": "absolute", 1407 | "steps": [ 1408 | { 1409 | "color": "semi-dark-red", 1410 | "value": null 1411 | }, 1412 | { 1413 | "color": "semi-dark-green", 1414 | "value": 0 1415 | } 1416 | ] 1417 | } 1418 | }, 1419 | "overrides": [ 1420 | { 1421 | "matcher": { 1422 | "id": "byName", 1423 | "options": "dev_cob" 1424 | }, 1425 | "properties": [ 1426 | { 1427 | "id": "color", 1428 | "value": { 1429 | "fixedColor": "#404040", 1430 | "mode": "fixed" 1431 | } 1432 | } 1433 | ] 1434 | } 1435 | ] 1436 | }, 1437 | "gridPos": { 1438 | "h": 3, 1439 | "w": 12, 1440 | "x": 3, 1441 | "y": 21 1442 | }, 1443 | "id": 7, 1444 | "options": { 1445 | "legend": { 1446 | "calcs": [], 1447 | "displayMode": "hidden", 1448 | "placement": "bottom" 1449 | }, 1450 | "tooltip": { 1451 | "mode": "multi", 1452 | "sort": "none" 1453 | } 1454 | }, 1455 | "targets": [ 1456 | { 1457 | "datasource": { 1458 | "type": "influxdb", 1459 | "uid": "${DS_INFLUXDB}" 1460 | }, 1461 | "hide": false, 1462 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r[\"_field\"] == \"dev\" or r[\"_field\"] == \"cob\")\n |> aggregateWindow(every: 5m, fn: last, createEmpty: false)\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\n |> map(fn: (r) => ({ r with _measurement: \"\", is_cob : if r.cob > 0 then 1 else 0, _value: r.dev }))\n |> filter(fn: (r) => r.is_cob <= 0)\n |> map(fn: (r) => ({ _time: r._time, dev: r.dev }))", 1463 | "refId": "A" 1464 | }, 1465 | { 1466 | "datasource": { 1467 | "type": "influxdb", 1468 | "uid": "${DS_INFLUXDB}" 1469 | }, 1470 | "hide": false, 1471 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r[\"_field\"] == \"dev\" or r[\"_field\"] == \"cob\")\n |> aggregateWindow(every: 5m, fn: last, createEmpty: false)\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\n |> map(fn: (r) => ({ r with _measurement: \"\", is_cob : if r.cob > 0 then 1 else 0, _value: r.dev }))\n |> filter(fn: (r) => r.is_cob > 0)\n |> map(fn: (r) => ({ _time: r._time, dev_cob: r.dev }))", 1472 | "refId": "B" 1473 | } 1474 | ], 1475 | "title": "dev", 1476 | "type": "timeseries" 1477 | }, 1478 | { 1479 | "datasource": { 1480 | "type": "influxdb", 1481 | "uid": "${DS_INFLUXDB}" 1482 | }, 1483 | "fieldConfig": { 1484 | "defaults": { 1485 | "color": { 1486 | "fixedColor": "blue", 1487 | "mode": "thresholds" 1488 | }, 1489 | "custom": { 1490 | "axisLabel": "", 1491 | "axisPlacement": "hidden", 1492 | "barAlignment": 0, 1493 | "drawStyle": "line", 1494 | "fillOpacity": 35, 1495 | "gradientMode": "scheme", 1496 | "hideFrom": { 1497 | "legend": false, 1498 | "tooltip": false, 1499 | "viz": false 1500 | }, 1501 | "lineInterpolation": "smooth", 1502 | "lineWidth": 1, 1503 | "pointSize": 5, 1504 | "scaleDistribution": { 1505 | "type": "linear" 1506 | }, 1507 | "showPoints": "never", 1508 | "spanNulls": false, 1509 | "stacking": { 1510 | "group": "A", 1511 | "mode": "none" 1512 | }, 1513 | "thresholdsStyle": { 1514 | "mode": "line" 1515 | } 1516 | }, 1517 | "mappings": [], 1518 | "thresholds": { 1519 | "mode": "absolute", 1520 | "steps": [ 1521 | { 1522 | "color": "dark-yellow", 1523 | "value": null 1524 | }, 1525 | { 1526 | "color": "blue", 1527 | "value": 0 1528 | } 1529 | ] 1530 | } 1531 | }, 1532 | "overrides": [] 1533 | }, 1534 | "gridPos": { 1535 | "h": 3, 1536 | "w": 13, 1537 | "x": 3, 1538 | "y": 24 1539 | }, 1540 | "id": 21, 1541 | "options": { 1542 | "legend": { 1543 | "calcs": [ 1544 | "mean", 1545 | "min" 1546 | ], 1547 | "displayMode": "table", 1548 | "placement": "right" 1549 | }, 1550 | "tooltip": { 1551 | "mode": "multi", 1552 | "sort": "none" 1553 | } 1554 | }, 1555 | "targets": [ 1556 | { 1557 | "datasource": { 1558 | "type": "influxdb", 1559 | "uid": "${DS_INFLUXDB}" 1560 | }, 1561 | "hide": false, 1562 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r[\"_field\"] == \"iob\")", 1563 | "refId": "B" 1564 | } 1565 | ], 1566 | "title": "basal iob", 1567 | "type": "timeseries" 1568 | }, 1569 | { 1570 | "collapsed": true, 1571 | "gridPos": { 1572 | "h": 1, 1573 | "w": 24, 1574 | "x": 0, 1575 | "y": 27 1576 | }, 1577 | "id": 14, 1578 | "panels": [ 1579 | { 1580 | "datasource": { 1581 | "type": "influxdb", 1582 | "uid": "${DS_INFLUXDB}" 1583 | }, 1584 | "fieldConfig": { 1585 | "defaults": { 1586 | "color": { 1587 | "fixedColor": "blue", 1588 | "mode": "fixed" 1589 | }, 1590 | "custom": { 1591 | "axisLabel": "", 1592 | "axisPlacement": "left", 1593 | "axisSoftMin": 0, 1594 | "barAlignment": 0, 1595 | "drawStyle": "line", 1596 | "fillOpacity": 0, 1597 | "gradientMode": "none", 1598 | "hideFrom": { 1599 | "legend": false, 1600 | "tooltip": false, 1601 | "viz": false 1602 | }, 1603 | "lineInterpolation": "smooth", 1604 | "lineWidth": 1, 1605 | "pointSize": 5, 1606 | "scaleDistribution": { 1607 | "type": "linear" 1608 | }, 1609 | "showPoints": "auto", 1610 | "spanNulls": false, 1611 | "stacking": { 1612 | "group": "A", 1613 | "mode": "none" 1614 | }, 1615 | "thresholdsStyle": { 1616 | "mode": "off" 1617 | } 1618 | }, 1619 | "mappings": [], 1620 | "thresholds": { 1621 | "mode": "absolute", 1622 | "steps": [ 1623 | { 1624 | "color": "blue" 1625 | } 1626 | ] 1627 | } 1628 | }, 1629 | "overrides": [] 1630 | }, 1631 | "gridPos": { 1632 | "h": 8, 1633 | "w": 24, 1634 | "x": 0, 1635 | "y": 28 1636 | }, 1637 | "id": 3, 1638 | "options": { 1639 | "legend": { 1640 | "calcs": [], 1641 | "displayMode": "list", 1642 | "placement": "bottom" 1643 | }, 1644 | "tooltip": { 1645 | "mode": "single", 1646 | "sort": "none" 1647 | } 1648 | }, 1649 | "targets": [ 1650 | { 1651 | "datasource": { 1652 | "type": "influxdb", 1653 | "uid": "${DS_INFLUXDB}" 1654 | }, 1655 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r[\"_field\"] == \"iob\")\n |> aggregateWindow(every: 1m, fn: last, createEmpty: false)", 1656 | "refId": "A" 1657 | }, 1658 | { 1659 | "datasource": { 1660 | "type": "influxdb", 1661 | "uid": "${DS_INFLUXDB}" 1662 | }, 1663 | "hide": false, 1664 | "query": "from(bucket: \"ns\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"openaps\")\n |> filter(fn: (r) => r[\"_field\"] == \"insulin_req\")\n |> aggregateWindow(every: 1m, fn: last, createEmpty: false)", 1665 | "refId": "B" 1666 | } 1667 | ], 1668 | "title": "IOB", 1669 | "type": "timeseries" 1670 | }, 1671 | { 1672 | "datasource": { 1673 | "type": "influxdb", 1674 | "uid": "${DS_INFLUXDB_QL}" 1675 | }, 1676 | "fieldConfig": { 1677 | "defaults": { 1678 | "color": { 1679 | "mode": "thresholds" 1680 | }, 1681 | "custom": { 1682 | "axisLabel": "", 1683 | "axisPlacement": "auto", 1684 | "barAlignment": 0, 1685 | "drawStyle": "line", 1686 | "fillOpacity": 0, 1687 | "gradientMode": "scheme", 1688 | "hideFrom": { 1689 | "legend": false, 1690 | "tooltip": false, 1691 | "viz": false 1692 | }, 1693 | "lineInterpolation": "linear", 1694 | "lineWidth": 1, 1695 | "pointSize": 5, 1696 | "scaleDistribution": { 1697 | "type": "linear" 1698 | }, 1699 | "showPoints": "auto", 1700 | "spanNulls": false, 1701 | "stacking": { 1702 | "group": "A", 1703 | "mode": "none" 1704 | }, 1705 | "thresholdsStyle": { 1706 | "mode": "line" 1707 | } 1708 | }, 1709 | "mappings": [], 1710 | "min": 0, 1711 | "thresholds": { 1712 | "mode": "absolute", 1713 | "steps": [ 1714 | { 1715 | "color": "dark-red" 1716 | }, 1717 | { 1718 | "color": "green", 1719 | "value": 3.9 1720 | }, 1721 | { 1722 | "color": "#EAB839", 1723 | "value": 10 1724 | } 1725 | ] 1726 | } 1727 | }, 1728 | "overrides": [ 1729 | { 1730 | "matcher": { 1731 | "id": "byName", 1732 | "options": "iob" 1733 | }, 1734 | "properties": [ 1735 | { 1736 | "id": "color", 1737 | "value": { 1738 | "fixedColor": "light-blue", 1739 | "mode": "fixed" 1740 | } 1741 | }, 1742 | { 1743 | "id": "custom.showPoints", 1744 | "value": "never" 1745 | } 1746 | ] 1747 | }, 1748 | { 1749 | "matcher": { 1750 | "id": "byName", 1751 | "options": "insulin_req" 1752 | }, 1753 | "properties": [ 1754 | { 1755 | "id": "color", 1756 | "value": { 1757 | "fixedColor": "dark-blue", 1758 | "mode": "fixed" 1759 | } 1760 | }, 1761 | { 1762 | "id": "custom.showPoints", 1763 | "value": "never" 1764 | } 1765 | ] 1766 | }, 1767 | { 1768 | "matcher": { 1769 | "id": "byName", 1770 | "options": "bg" 1771 | }, 1772 | "properties": [ 1773 | { 1774 | "id": "custom.lineWidth", 1775 | "value": 2 1776 | }, 1777 | { 1778 | "id": "custom.fillOpacity", 1779 | "value": 15 1780 | } 1781 | ] 1782 | }, 1783 | { 1784 | "matcher": { 1785 | "id": "byName", 1786 | "options": "bolus" 1787 | }, 1788 | "properties": [ 1789 | { 1790 | "id": "custom.drawStyle", 1791 | "value": "bars" 1792 | }, 1793 | { 1794 | "id": "custom.axisPlacement", 1795 | "value": "right" 1796 | }, 1797 | { 1798 | "id": "custom.axisSoftMax", 1799 | "value": 1.5 1800 | } 1801 | ] 1802 | } 1803 | ] 1804 | }, 1805 | "gridPos": { 1806 | "h": 10, 1807 | "w": 12, 1808 | "x": 0, 1809 | "y": 36 1810 | }, 1811 | "id": 5, 1812 | "interval": "1m", 1813 | "options": { 1814 | "legend": { 1815 | "calcs": [], 1816 | "displayMode": "list", 1817 | "placement": "bottom" 1818 | }, 1819 | "tooltip": { 1820 | "mode": "multi", 1821 | "sort": "none" 1822 | } 1823 | }, 1824 | "targets": [ 1825 | { 1826 | "alias": "$col", 1827 | "datasource": { 1828 | "type": "influxdb", 1829 | "uid": "${DS_INFLUXDB_QL}" 1830 | }, 1831 | "groupBy": [], 1832 | "measurement": "openaps", 1833 | "orderByTime": "ASC", 1834 | "policy": "ns", 1835 | "query": "SELECT \"bg\" / 18.0 FROM \"ns\".\"openaps\" WHERE $timeFilter", 1836 | "rawQuery": false, 1837 | "refId": "A", 1838 | "resultFormat": "time_series", 1839 | "select": [ 1840 | [ 1841 | { 1842 | "params": [ 1843 | "bg" 1844 | ], 1845 | "type": "field" 1846 | }, 1847 | { 1848 | "params": [ 1849 | " / 18.0" 1850 | ], 1851 | "type": "math" 1852 | } 1853 | ] 1854 | ], 1855 | "tags": [] 1856 | }, 1857 | { 1858 | "alias": "$col", 1859 | "datasource": { 1860 | "type": "influxdb", 1861 | "uid": "${DS_INFLUXDB_QL}" 1862 | }, 1863 | "groupBy": [], 1864 | "hide": false, 1865 | "measurement": "openaps", 1866 | "orderByTime": "ASC", 1867 | "policy": "ns", 1868 | "refId": "B", 1869 | "resultFormat": "time_series", 1870 | "select": [ 1871 | [ 1872 | { 1873 | "params": [ 1874 | "iob" 1875 | ], 1876 | "type": "field" 1877 | } 1878 | ] 1879 | ], 1880 | "tags": [] 1881 | }, 1882 | { 1883 | "alias": "$col", 1884 | "datasource": { 1885 | "type": "influxdb", 1886 | "uid": "${DS_INFLUXDB_QL}" 1887 | }, 1888 | "groupBy": [], 1889 | "hide": false, 1890 | "measurement": "openaps", 1891 | "orderByTime": "ASC", 1892 | "policy": "ns", 1893 | "refId": "C", 1894 | "resultFormat": "time_series", 1895 | "select": [ 1896 | [ 1897 | { 1898 | "params": [ 1899 | "insulin_req" 1900 | ], 1901 | "type": "field" 1902 | } 1903 | ] 1904 | ], 1905 | "tags": [] 1906 | }, 1907 | { 1908 | "alias": "$col", 1909 | "datasource": { 1910 | "type": "influxdb", 1911 | "uid": "${DS_INFLUXDB_QL}" 1912 | }, 1913 | "groupBy": [], 1914 | "hide": false, 1915 | "measurement": "openaps", 1916 | "orderByTime": "ASC", 1917 | "policy": "default", 1918 | "refId": "D", 1919 | "resultFormat": "time_series", 1920 | "select": [ 1921 | [ 1922 | { 1923 | "params": [ 1924 | "bolus" 1925 | ], 1926 | "type": "field" 1927 | } 1928 | ] 1929 | ], 1930 | "tags": [] 1931 | } 1932 | ], 1933 | "title": "BG", 1934 | "type": "timeseries" 1935 | } 1936 | ], 1937 | "title": "Row title", 1938 | "type": "row" 1939 | } 1940 | ], 1941 | "refresh": "1m", 1942 | "schemaVersion": 36, 1943 | "style": "dark", 1944 | "tags": [], 1945 | "templating": { 1946 | "list": [] 1947 | }, 1948 | "time": { 1949 | "from": "now-6h", 1950 | "to": "now" 1951 | }, 1952 | "timepicker": {}, 1953 | "timezone": "", 1954 | "title": "Nightscout data", 1955 | "uid": "8XquEMC7z", 1956 | "version": 95, 1957 | "weekStart": "" 1958 | } --------------------------------------------------------------------------------