mirror of
https://github.com/jwoglom/tconnectsync.git
synced 2026-08-25 18:24:13 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a400390de | ||
|
|
e2b30687e4 | ||
|
|
0d956516d7 | ||
|
|
50cb57f11b | ||
|
|
2e515efc54 | ||
|
|
b52006b3b0 | ||
|
|
07e5761178 | ||
|
|
631d1c7b89 | ||
|
|
0253956377 | ||
|
|
59816225e0 | ||
|
|
be113e9cf7 | ||
|
|
90bede8b75 | ||
|
|
7a8d00da88 | ||
|
|
52cb287ce8 | ||
|
|
27ba5d9a64 | ||
|
|
2140bd356e | ||
|
|
a74b63ab05 |
@@ -7,3 +7,4 @@ build
|
||||
*.swa
|
||||
*.egg-info
|
||||
.env
|
||||
tconnectsync-check-output.log
|
||||
|
||||
Generated
+88
-76
@@ -16,11 +16,11 @@
|
||||
"default": {
|
||||
"arrow": {
|
||||
"hashes": [
|
||||
"sha256:16fc29bbd9e425e3eb0fef3018297910a0f4568f21116fc31771e2760a50e074",
|
||||
"sha256:8fb7d9d3d4bf90e49e734c22fa077bdd0964135c4b8120de2510575a8d1f620c"
|
||||
"sha256:6b2914ef3997d1fd7b37a71ce9dd61a6e329d09e1c7b44f4d3099ca4a5c0933e",
|
||||
"sha256:c2dde3c382d9f7e6922ce636bf0b318a7a853df40ecb383b29192e6c5cc82840"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.2.0"
|
||||
"version": "==1.2.1"
|
||||
},
|
||||
"beautifulsoup4": {
|
||||
"hashes": [
|
||||
@@ -46,11 +46,11 @@
|
||||
},
|
||||
"charset-normalizer": {
|
||||
"hashes": [
|
||||
"sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0",
|
||||
"sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"
|
||||
"sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721",
|
||||
"sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"version": "==2.0.7"
|
||||
"version": "==2.0.9"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
@@ -62,57 +62,69 @@
|
||||
},
|
||||
"lxml": {
|
||||
"hashes": [
|
||||
"sha256:079f3ae844f38982d156efce585bc540c16a926d4436712cf4baee0cce487a3d",
|
||||
"sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3",
|
||||
"sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2",
|
||||
"sha256:1b38116b6e628118dea5b2186ee6820ab138dbb1e24a13e478490c7db2f326ae",
|
||||
"sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f",
|
||||
"sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927",
|
||||
"sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3",
|
||||
"sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7",
|
||||
"sha256:3082c518be8e97324390614dacd041bb1358c882d77108ca1957ba47738d9d59",
|
||||
"sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f",
|
||||
"sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade",
|
||||
"sha256:36108c73739985979bf302006527cf8a20515ce444ba916281d1c43938b8bb96",
|
||||
"sha256:39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468",
|
||||
"sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b",
|
||||
"sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4",
|
||||
"sha256:4c61b3a0db43a1607d6264166b230438f85bfed02e8cff20c22e564d0faff354",
|
||||
"sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83",
|
||||
"sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04",
|
||||
"sha256:5c8c163396cc0df3fd151b927e74f6e4acd67160d6c33304e805b84293351d16",
|
||||
"sha256:64812391546a18896adaa86c77c59a4998f33c24788cadc35789e55b727a37f4",
|
||||
"sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791",
|
||||
"sha256:6f12e1427285008fd32a6025e38e977d44d6382cf28e7201ed10d6c1698d2a9a",
|
||||
"sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51",
|
||||
"sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1",
|
||||
"sha256:76fa7b1362d19f8fbd3e75fe2fb7c79359b0af8747e6f7141c338f0bee2f871a",
|
||||
"sha256:7728e05c35412ba36d3e9795ae8995e3c86958179c9770e65558ec3fdfd3724f",
|
||||
"sha256:8157dadbb09a34a6bd95a50690595e1fa0af1a99445e2744110e3dca7831c4ee",
|
||||
"sha256:820628b7b3135403540202e60551e741f9b6d3304371712521be939470b454ec",
|
||||
"sha256:884ab9b29feaca361f7f88d811b1eea9bfca36cf3da27768d28ad45c3ee6f969",
|
||||
"sha256:89b8b22a5ff72d89d48d0e62abb14340d9e99fd637d046c27b8b257a01ffbe28",
|
||||
"sha256:92e821e43ad382332eade6812e298dc9701c75fe289f2a2d39c7960b43d1e92a",
|
||||
"sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa",
|
||||
"sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106",
|
||||
"sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d",
|
||||
"sha256:c1a40c06fd5ba37ad39caa0b3144eb3772e813b5fb5b084198a985431c2f1e8d",
|
||||
"sha256:c47ff7e0a36d4efac9fd692cfa33fbd0636674c102e9e8d9b26e1b93a94e7617",
|
||||
"sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4",
|
||||
"sha256:cdaf11d2bd275bf391b5308f86731e5194a21af45fbaaaf1d9e8147b9160ea92",
|
||||
"sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0",
|
||||
"sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4",
|
||||
"sha256:d916d31fd85b2f78c76400d625076d9124de3e4bda8b016d25a050cc7d603f24",
|
||||
"sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2",
|
||||
"sha256:e1cbd3f19a61e27e011e02f9600837b921ac661f0c40560eefb366e4e4fb275e",
|
||||
"sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0",
|
||||
"sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654",
|
||||
"sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2",
|
||||
"sha256:f8380c03e45cf09f8557bdaa41e1fa7c81f3ae22828e1db470ab2a6c96d8bc23",
|
||||
"sha256:f90ba11136bfdd25cae3951af8da2e95121c9b9b93727b1b896e3fa105b2f586"
|
||||
"sha256:0607ff0988ad7e173e5ddf7bf55ee65534bd18a5461183c33e8e41a59e89edf4",
|
||||
"sha256:09b738360af8cb2da275998a8bf79517a71225b0de41ab47339c2beebfff025f",
|
||||
"sha256:0a5f0e4747f31cff87d1eb32a6000bde1e603107f632ef4666be0dc065889c7a",
|
||||
"sha256:0b5e96e25e70917b28a5391c2ed3ffc6156513d3db0e1476c5253fcd50f7a944",
|
||||
"sha256:1104a8d47967a414a436007c52f533e933e5d52574cab407b1e49a4e9b5ddbd1",
|
||||
"sha256:13dbb5c7e8f3b6a2cf6e10b0948cacb2f4c9eb05029fe31c60592d08ac63180d",
|
||||
"sha256:2a906c3890da6a63224d551c2967413b8790a6357a80bf6b257c9a7978c2c42d",
|
||||
"sha256:317bd63870b4d875af3c1be1b19202de34c32623609ec803b81c99193a788c1e",
|
||||
"sha256:34c22eb8c819d59cec4444d9eebe2e38b95d3dcdafe08965853f8799fd71161d",
|
||||
"sha256:36b16fecb10246e599f178dd74f313cbdc9f41c56e77d52100d1361eed24f51a",
|
||||
"sha256:38d9759733aa04fb1697d717bfabbedb21398046bd07734be7cccc3d19ea8675",
|
||||
"sha256:3e26ad9bc48d610bf6cc76c506b9e5ad9360ed7a945d9be3b5b2c8535a0145e3",
|
||||
"sha256:41358bfd24425c1673f184d7c26c6ae91943fe51dfecc3603b5e08187b4bcc55",
|
||||
"sha256:447d5009d6b5447b2f237395d0018901dcc673f7d9f82ba26c1b9f9c3b444b60",
|
||||
"sha256:44f552e0da3c8ee3c28e2eb82b0b784200631687fc6a71277ea8ab0828780e7d",
|
||||
"sha256:490712b91c65988012e866c411a40cc65b595929ececf75eeb4c79fcc3bc80a6",
|
||||
"sha256:4c093c571bc3da9ebcd484e001ba18b8452903cd428c0bc926d9b0141bcb710e",
|
||||
"sha256:50d3dba341f1e583265c1a808e897b4159208d814ab07530202b6036a4d86da5",
|
||||
"sha256:534e946bce61fd162af02bad7bfd2daec1521b71d27238869c23a672146c34a5",
|
||||
"sha256:585ea241ee4961dc18a95e2f5581dbc26285fcf330e007459688096f76be8c42",
|
||||
"sha256:59e7da839a1238807226f7143c68a479dee09244d1b3cf8c134f2fce777d12d0",
|
||||
"sha256:5b0f782f0e03555c55e37d93d7a57454efe7495dab33ba0ccd2dbe25fc50f05d",
|
||||
"sha256:5bee1b0cbfdb87686a7fb0e46f1d8bd34d52d6932c0723a86de1cc532b1aa489",
|
||||
"sha256:610807cea990fd545b1559466971649e69302c8a9472cefe1d6d48a1dee97440",
|
||||
"sha256:6308062534323f0d3edb4e702a0e26a76ca9e0e23ff99be5d82750772df32a9e",
|
||||
"sha256:67fa5f028e8a01e1d7944a9fb616d1d0510d5d38b0c41708310bd1bc45ae89f6",
|
||||
"sha256:6a2ab9d089324d77bb81745b01f4aeffe4094306d939e92ba5e71e9a6b99b71e",
|
||||
"sha256:6c198bfc169419c09b85ab10cb0f572744e686f40d1e7f4ed09061284fc1303f",
|
||||
"sha256:6e56521538f19c4a6690f439fefed551f0b296bd785adc67c1777c348beb943d",
|
||||
"sha256:6ec829058785d028f467be70cd195cd0aaf1a763e4d09822584ede8c9eaa4b03",
|
||||
"sha256:718d7208b9c2d86aaf0294d9381a6acb0158b5ff0f3515902751404e318e02c9",
|
||||
"sha256:735e3b4ce9c0616e85f302f109bdc6e425ba1670a73f962c9f6b98a6d51b77c9",
|
||||
"sha256:772057fba283c095db8c8ecde4634717a35c47061d24f889468dc67190327bcd",
|
||||
"sha256:7b5e2acefd33c259c4a2e157119c4373c8773cf6793e225006a1649672ab47a6",
|
||||
"sha256:82d16a64236970cb93c8d63ad18c5b9f138a704331e4b916b2737ddfad14e0c4",
|
||||
"sha256:87c1b0496e8c87ec9db5383e30042357b4839b46c2d556abd49ec770ce2ad868",
|
||||
"sha256:8e54945dd2eeb50925500957c7c579df3cd07c29db7810b83cf30495d79af267",
|
||||
"sha256:9393a05b126a7e187f3e38758255e0edf948a65b22c377414002d488221fdaa2",
|
||||
"sha256:9fbc0dee7ff5f15c4428775e6fa3ed20003140560ffa22b88326669d53b3c0f4",
|
||||
"sha256:a1613838aa6b89af4ba10a0f3a972836128801ed008078f8c1244e65958f1b24",
|
||||
"sha256:a1bbc4efa99ed1310b5009ce7f3a1784698082ed2c1ef3895332f5df9b3b92c2",
|
||||
"sha256:a555e06566c6dc167fbcd0ad507ff05fd9328502aefc963cb0a0547cfe7f00db",
|
||||
"sha256:a58d78653ae422df6837dd4ca0036610b8cb4962b5cfdbd337b7b24de9e5f98a",
|
||||
"sha256:a5edc58d631170de90e50adc2cc0248083541affef82f8cd93bea458e4d96db8",
|
||||
"sha256:a5f623aeaa24f71fce3177d7fee875371345eb9102b355b882243e33e04b7175",
|
||||
"sha256:adaab25be351fff0d8a691c4f09153647804d09a87a4e4ea2c3f9fe9e8651851",
|
||||
"sha256:ade74f5e3a0fd17df5782896ddca7ddb998845a5f7cd4b0be771e1ffc3b9aa5b",
|
||||
"sha256:b1d381f58fcc3e63fcc0ea4f0a38335163883267f77e4c6e22d7a30877218a0e",
|
||||
"sha256:bf6005708fc2e2c89a083f258b97709559a95f9a7a03e59f805dd23c93bc3986",
|
||||
"sha256:d546431636edb1d6a608b348dd58cc9841b81f4116745857b6cb9f8dadb2725f",
|
||||
"sha256:d5618d49de6ba63fe4510bdada62d06a8acfca0b4b5c904956c777d28382b419",
|
||||
"sha256:dfd0d464f3d86a1460683cd742306d1138b4e99b79094f4e07e1ca85ee267fe7",
|
||||
"sha256:e18281a7d80d76b66a9f9e68a98cf7e1d153182772400d9a9ce855264d7d0ce7",
|
||||
"sha256:e410cf3a2272d0a85526d700782a2fa92c1e304fdcc519ba74ac80b8297adf36",
|
||||
"sha256:e662c6266e3a275bdcb6bb049edc7cd77d0b0f7e119a53101d367c841afc66dc",
|
||||
"sha256:ec9027d0beb785a35aa9951d14e06d48cfbf876d8ff67519403a2522b181943b",
|
||||
"sha256:eed394099a7792834f0cb4a8f615319152b9d801444c1c9e1b1a2c36d2239f9e",
|
||||
"sha256:f76dbe44e31abf516114f6347a46fa4e7c2e8bceaa4b6f7ee3a0a03c8eba3c17",
|
||||
"sha256:fc15874816b9320581133ddc2096b644582ab870cf6a6ed63684433e7af4b0d3",
|
||||
"sha256:fc9fb11b65e7bc49f7f75aaba1b700f7181d95d4e151cf2f24d51bfd14410b77"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.6.3"
|
||||
"version": "==4.7.1"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
@@ -124,19 +136,19 @@
|
||||
},
|
||||
"python-dotenv": {
|
||||
"hashes": [
|
||||
"sha256:14f8185cc8d494662683e6914addcb7e95374771e707601dfc70166946b4c4b8",
|
||||
"sha256:bbd3da593fc49c249397cbfbcc449cf36cb02e75afc8157fcc6a81df6fb7750a"
|
||||
"sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3",
|
||||
"sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.19.1"
|
||||
"version": "==0.19.2"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
|
||||
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
|
||||
"sha256:8e5643905bf20a308e25e4c1dd379117c09000bf8a82ebccc462cfb1b34a16b5",
|
||||
"sha256:f71a09d7feba4a6b64ffd8e9d9bc60f9bf7d7e19fd0e04362acb1cfc2e3d98df"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.26.0"
|
||||
"version": "==2.27.0"
|
||||
},
|
||||
"requests-mock": {
|
||||
"hashes": [
|
||||
@@ -156,11 +168,11 @@
|
||||
},
|
||||
"soupsieve": {
|
||||
"hashes": [
|
||||
"sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc",
|
||||
"sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b"
|
||||
"sha256:1a3cca2617c6b38c0343ed661b1fa5de5637f257d4fe22bd9f1338010a1efefb",
|
||||
"sha256:b8d49b1cd4f037c7082a9683dfa1801aa2597fb11c3a1155b7a5b94829b4f1f9"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.2.1"
|
||||
"version": "==2.3.1"
|
||||
},
|
||||
"tconnectsync": {
|
||||
"editable": true,
|
||||
@@ -185,27 +197,27 @@
|
||||
},
|
||||
"jedi": {
|
||||
"hashes": [
|
||||
"sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93",
|
||||
"sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"
|
||||
"sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d",
|
||||
"sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.18.0"
|
||||
"version": "==0.18.1"
|
||||
},
|
||||
"parso": {
|
||||
"hashes": [
|
||||
"sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398",
|
||||
"sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"
|
||||
"sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0",
|
||||
"sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.8.2"
|
||||
"version": "==0.8.3"
|
||||
},
|
||||
"prompt-toolkit": {
|
||||
"hashes": [
|
||||
"sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c",
|
||||
"sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"
|
||||
"sha256:1bb05628c7d87b645974a1bad3f17612be0c29fa39af9f7688030163f680bad6",
|
||||
"sha256:e56f2ff799bacecd3e88165b1e2f5ebf9bcd59e80e06d395fa0cc4b8bd7bb506"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.2'",
|
||||
"version": "==3.0.20"
|
||||
"version": "==3.0.24"
|
||||
},
|
||||
"ptpython": {
|
||||
"hashes": [
|
||||
@@ -217,11 +229,11 @@
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380",
|
||||
"sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"
|
||||
"sha256:59b895e326f0fb0d733fd28c6839bd18ad0687ba20efc26d4277fd1d30b971f4",
|
||||
"sha256:9135c1af61eec0f650cd1ea1ed8ce298e54d56bcd8cc2ef46edd7702c171337c"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.10.0"
|
||||
"version": "==2.11.1"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
|
||||
@@ -40,15 +40,23 @@ The following synchronization feature is disabled by default, but can be enabled
|
||||
|
||||
## Setup
|
||||
|
||||
**To get started,** you need to choose whether to install the application via
|
||||
The following setup instructions assume that you have a Linux, MacOS, or Windows (with WSL) machine that will run the application continuously.
|
||||
If you've configured Nightscout before, you may be familiar with Heroku. [You can opt to run tconnectsync with Heroku by following these instructions.](https://github.com/jwoglom/tconnectsync-heroku)
|
||||
|
||||
**To get started,** you need to choose whether to install the application on your computer via
|
||||
**Pip**, **Pipenv**, or **Docker**.
|
||||
|
||||
After that, you can choose to run the program continuously via **Supervisord**
|
||||
or on a regular interval with **Cron**.
|
||||
|
||||
**NOTE:** If you fork the tconnectsync repository on GitHub, **do not commit your .env file**.
|
||||
If pushed to GitHub, this will make your tconnect and Nightscout passwords publicly visible and put your data at risk.
|
||||
|
||||
## Installation
|
||||
|
||||
First, create a file named `.env` containing configuration values.
|
||||
First, you need to create a file containing configuration values.
|
||||
The name of this file will be `.env`, and its location will be dependent on which
|
||||
method of installation you choose.
|
||||
You should specify the following parameters:
|
||||
|
||||
```bash
|
||||
@@ -67,21 +75,31 @@ NS_SECRET='apisecret'
|
||||
TIMEZONE_NAME='America/New_York'
|
||||
```
|
||||
|
||||
These values can alternatively be specified via environment variables.
|
||||
This file contains your t:connect username and password, Tandem pump serial number (which is utilized in API calls to t:connect), your Nightscout URL and secret token (for uploading data to Nightscout), and local timezone (the timezone used in t:connect). When specifying the timezone, enter a [TZ database name value](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
|
||||
|
||||
The .env file contains your t:connect username and password, Tandem pump serial number (which is utilized in API calls to t:connect), your Nightscout URL and secret token (for uploading data to Nightscout), and local timezone (the timezone used in t:connect).
|
||||
(Alternatively, these values can be specified via environment variables.)
|
||||
|
||||
### Installation via Pip
|
||||
|
||||
This is the easiest method to install.
|
||||
|
||||
First, ensure that you have **Python 3** with **Pip** installed on your
|
||||
Linux machine. Then, install tconnectsync from pip:
|
||||
First, ensure that you have **Python 3** with **Pip** installed:
|
||||
|
||||
* **On MacOS:** Open Terminal. Install [Homebrew](https://brew.sh/), and then run `brew install python3`
|
||||
* **On Linux:** Follow your distribution's specific instructions.
|
||||
For Debian/Ubuntu based distros, `sudo apt install python3 python3-pip`
|
||||
* **On Windows:** Install Ubuntu under the [Windows Subsystem for Linux](https://ubuntu.com/wsl).
|
||||
Open the Ubuntu Terminal, then run `sudo apt install python3 python3-pip`.
|
||||
Perform the remainder of the steps under the Ubuntu environment.
|
||||
|
||||
Now install the `tconnectsync` package with pip:
|
||||
|
||||
```
|
||||
$ pip3 install tconnectsync
|
||||
```
|
||||
|
||||
If the pip3 command is not found, run `python3 -m pip install tconnectsync` instead.
|
||||
|
||||
After this, you should be able to view tconnectsync's help with:
|
||||
```
|
||||
$ tconnectsync --help
|
||||
@@ -105,7 +123,12 @@ optional arguments:
|
||||
Specifies what data should be synchronized between tconnect and Nightscout.
|
||||
```
|
||||
|
||||
Go to the folder where you created the `.env` file, and run:
|
||||
Move the `.env` file you created to the following folder:
|
||||
|
||||
* **MacOS:** `/Users/<username>/.config/tconnectsync/.env`
|
||||
* **Linux:** `$HOME/.config/tconnectsync/.env`
|
||||
* **Windows:** `$HOME/.config/tconnectsync/.env` (inside WSL)
|
||||
|
||||
```
|
||||
$ tconnectsync --check-login
|
||||
```
|
||||
@@ -154,7 +177,8 @@ optional arguments:
|
||||
```
|
||||
|
||||
|
||||
Move the `.env` file you created earlier into this folder, and run:
|
||||
Move the `.env` file you created earlier into the `tconnectsync` folder, and run:
|
||||
|
||||
```
|
||||
$ pipenv run tconnectsync --check-login
|
||||
```
|
||||
@@ -172,6 +196,17 @@ $ docker pull ghcr.io/jwoglom/tconnectsync/tconnectsync:latest
|
||||
$ docker run ghcr.io/jwoglom/tconnectsync/tconnectsync --help
|
||||
```
|
||||
|
||||
Move the `.env` file you created earlier into the current folder, and run:
|
||||
|
||||
```
|
||||
$ docker run tconnectsync --check-login
|
||||
```
|
||||
|
||||
If you receive no errors, then you can move on to the **Running Tconnectsync Continuously** section.
|
||||
|
||||
|
||||
#### Building Locally
|
||||
|
||||
To instead build the image locally and launch the project:
|
||||
|
||||
```bash
|
||||
@@ -183,9 +218,11 @@ $ docker run tconnectsync --help
|
||||
|
||||
Move the `.env` file you created earlier into this folder, and run:
|
||||
```
|
||||
$ docker run tconnectsync --check-login
|
||||
$ docker run --env-file=.env tconnectsync --check-login
|
||||
```
|
||||
|
||||
**NOTE:** If using the `--env-file` option to `docker run`, you may need to remove all quotation marks (`'` and `"`s) around values in the `.env` file for Docker to propagate the variables correctly.
|
||||
|
||||
If you receive no errors, then you can move on to the **Running Tconnectsync Continuously** section.
|
||||
|
||||
## Running Tconnectsync Continuously
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = tconnectsync
|
||||
version = 0.6
|
||||
version = 0.6.4
|
||||
author = James Woglom
|
||||
author_email = j@wogloms.net
|
||||
description = Syncs Tandem t:connect pump data to Nightscout for the t:slim X2
|
||||
@@ -34,4 +34,4 @@ exclude =
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
tconnectsync = tconnectsync:main
|
||||
tconnectsync = tconnectsync:main
|
||||
|
||||
@@ -30,7 +30,7 @@ except Exception:
|
||||
__version__ = "UNKNOWN"
|
||||
|
||||
def parse_args(*args, **kwargs):
|
||||
parser = argparse.ArgumentParser(description="Syncs bolus, basal, and IOB data from Tandem Diabetes t:connect to Nightscout.")
|
||||
parser = argparse.ArgumentParser(description="Syncs bolus, basal, and IOB data from Tandem Diabetes t:connect to Nightscout.", epilog="Version %s" % __version__)
|
||||
parser.add_argument('--version', action='version', version='tconnectsync %s' % __version__)
|
||||
parser.add_argument('--pretend', dest='pretend', action='store_const', const=True, default=False, help='Pretend mode: do not upload any data to Nightscout.')
|
||||
parser.add_argument('-v', '--verbose', dest='verbose', action='store_const', const=True, default=False, help='Verbose mode: show extra logging details')
|
||||
|
||||
@@ -2,9 +2,12 @@ import time
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from .api import TConnectApi
|
||||
from .process import process_time_range
|
||||
from .features import DEFAULT_FEATURES
|
||||
from .secret import (
|
||||
TCONNECT_EMAIL,
|
||||
TCONNECT_PASSWORD,
|
||||
PUMP_SERIAL_NUMBER,
|
||||
AUTOUPDATE_DEFAULT_SLEEP_SECONDS,
|
||||
AUTOUPDATE_MAX_SLEEP_SECONDS,
|
||||
@@ -42,6 +45,9 @@ def process_auto_update(tconnect, nightscout, time_start, time_end, pretend, fea
|
||||
if last_event_index:
|
||||
logger.error('An event index change was recorded, but no new data was found via the API. ' +
|
||||
'If this error reoccurs, try restarting tconnectsync.')
|
||||
|
||||
logger.info('Resetting TConnectApi')
|
||||
tconnect = TConnectApi(TCONNECT_EMAIL, TCONNECT_PASSWORD)
|
||||
else:
|
||||
last_process_time_range = now
|
||||
|
||||
|
||||
+131
-27
@@ -1,58 +1,162 @@
|
||||
import sys
|
||||
import time
|
||||
import pkg_resources
|
||||
from datetime import datetime
|
||||
|
||||
from .nightscout import NightscoutApi
|
||||
from .parser.nightscout import BASAL_EVENTTYPE, BOLUS_EVENTTYPE
|
||||
|
||||
try:
|
||||
__version__ = pkg_resources.require("tconnectsync")[0].version
|
||||
except Exception:
|
||||
__version__ = "UNKNOWN"
|
||||
|
||||
"""
|
||||
Attempts to authenticate with each t:connect API,
|
||||
and returns the output of a sample API call from each.
|
||||
Also attempts to connect to the Nightscout API.
|
||||
"""
|
||||
def check_login(tconnect, time_start, time_end):
|
||||
def check_login(tconnect, time_start, time_end, verbose=False):
|
||||
errors = 0
|
||||
|
||||
print("Logging in to t:connect ControlIQ API...")
|
||||
loglines = []
|
||||
def log(*args):
|
||||
print(*args)
|
||||
loglines.append(" ".join([str(i) for i in args]) + "\n")
|
||||
|
||||
def debug(*args):
|
||||
if verbose:
|
||||
print(*args)
|
||||
loglines.append(" ".join([str(i) for i in args]) + "\n")
|
||||
|
||||
log("tconnectsync version %s" % __version__)
|
||||
log("Python version %s" % sys.version)
|
||||
log("System platform %s" % sys.platform)
|
||||
log("Running checks with time range %s to %s" % (time_start, time_end))
|
||||
log("Current time: %s" % datetime.now())
|
||||
log("time.tzname: %s" % str(time.tzname))
|
||||
|
||||
log("Loading secrets...")
|
||||
try:
|
||||
from .secret import TCONNECT_EMAIL, TCONNECT_PASSWORD, PUMP_SERIAL_NUMBER, NS_URL, NS_SECRET, TIMEZONE_NAME
|
||||
except ImportError as e:
|
||||
log("Error: Unable to load config file. Please check your .env file or environment variables")
|
||||
log(e)
|
||||
|
||||
if not TCONNECT_EMAIL or TCONNECT_EMAIL == 'email@email.com':
|
||||
log("Error: You have not specified a TCONNECT_EMAIL")
|
||||
errors += 1
|
||||
|
||||
if not TCONNECT_PASSWORD or TCONNECT_PASSWORD == 'password':
|
||||
log("Error: You have not specified a TCONNECT_PASSWORD")
|
||||
errors += 1
|
||||
|
||||
if not PUMP_SERIAL_NUMBER or PUMP_SERIAL_NUMBER == '11111111':
|
||||
log("Error: You have not specified a PUMP_SERIAL_NUMBER")
|
||||
errors += 1
|
||||
|
||||
if not NS_URL or NS_URL == 'https://yournightscouturl/':
|
||||
log("Error: You have not specified a NS_URL")
|
||||
errors += 1
|
||||
|
||||
if not NS_SECRET or NS_SECRET == 'apisecret':
|
||||
log("Error: You have not specified a NS_SECRET")
|
||||
errors += 1
|
||||
|
||||
log("TIMEZONE_NAME: %s" % TIMEZONE_NAME)
|
||||
|
||||
log("-----")
|
||||
|
||||
log("Logging in to t:connect ControlIQ API...")
|
||||
try:
|
||||
summary = tconnect.controliq.dashboard_summary(time_start, time_end)
|
||||
print("ControlIQ dashboard summary: %s" % summary)
|
||||
debug("ControlIQ dashboard summary: %s" % summary)
|
||||
except Exception as e:
|
||||
print("Error occurred querying ControlIQ API: %s" % e)
|
||||
log("Error occurred querying ControlIQ API:")
|
||||
log(e)
|
||||
errors += 1
|
||||
|
||||
log("Querying ControlIQ therapy_timeline...")
|
||||
try:
|
||||
tt = tconnect.controliq.therapy_timeline(time_start, time_end)
|
||||
debug("ControlIQ therapy_timeline: %s" % tt)
|
||||
except Exception as e:
|
||||
log("Error occurred querying ControlIQ therapy_timeline:")
|
||||
log(e)
|
||||
errors += 1
|
||||
|
||||
log("-----")
|
||||
|
||||
print("\nLogging in to t:connect WS2 API...")
|
||||
log("Logging in to t:connect WS2 API...")
|
||||
try:
|
||||
summary = tconnect.ws2.basaliqtech(time_start, time_end)
|
||||
print("WS2 basaliq status: %s" % summary)
|
||||
debug("WS2 basaliq status: %s" % summary)
|
||||
except Exception as e:
|
||||
print("Error occurred querying WS2 API: %s" % e)
|
||||
log("Error occurred querying WS2 API:")
|
||||
log(e)
|
||||
errors += 1
|
||||
|
||||
log("Querying WS2 therapy_timeline_csv...")
|
||||
try:
|
||||
ttcsv = tconnect.ws2.therapy_timeline_csv(time_start, time_end)
|
||||
debug("therapy_timeline_csv: %s", ttcsv)
|
||||
except Exception as e:
|
||||
log("Error occurred querying WS2 therapy_timeline_csv:")
|
||||
log(e)
|
||||
errors += 1
|
||||
|
||||
log("-----")
|
||||
|
||||
print("\nLogging in to t:connect Android API...")
|
||||
log("Logging in to t:connect Android API...")
|
||||
try:
|
||||
summary = tconnect.android.user_profile()
|
||||
print("Android user profile: %s" % summary)
|
||||
|
||||
from .secret import PUMP_SERIAL_NUMBER
|
||||
|
||||
debug("Android user profile: %s" % summary)
|
||||
event = tconnect.android.last_event_uploaded(PUMP_SERIAL_NUMBER)
|
||||
print("\nAndroid last uploaded event: %s" % event)
|
||||
except ImportError:
|
||||
print("Error: Unable to load config file.")
|
||||
debug("Android last uploaded event: %s" % event)
|
||||
except Exception as e:
|
||||
print("Error occurred querying Android API: %s" % e)
|
||||
log("Error occurred querying Android API:")
|
||||
log(e)
|
||||
errors += 1
|
||||
|
||||
print("\nLogging in to Nightscout...")
|
||||
|
||||
log("Querying Android therapy_events...")
|
||||
try:
|
||||
from .secret import NS_URL, NS_SECRET
|
||||
|
||||
status = NightscoutApi(NS_URL, NS_SECRET).api_status()
|
||||
print("\nNightscout status: %s" % status)
|
||||
except ImportError:
|
||||
print("Error: Unable to load config file.")
|
||||
androidevents = tconnect.android.therapy_events(time_start, time_end)
|
||||
debug("android therapy_events: %s" % androidevents)
|
||||
except Exception as e:
|
||||
print("Error occurred querying Nightscout API: %s" % e)
|
||||
log("Error occurred querying Android therapy_events:")
|
||||
log(e)
|
||||
errors += 1
|
||||
|
||||
log("-----")
|
||||
|
||||
log("Logging in to Nightscout...")
|
||||
try:
|
||||
nightscout = NightscoutApi(NS_URL, NS_SECRET)
|
||||
status = nightscout.api_status()
|
||||
debug("Nightscout status: %s" % status)
|
||||
|
||||
last_upload_basal = nightscout.last_uploaded_entry(BASAL_EVENTTYPE)
|
||||
debug("Nightscout last uploaded basal: %s" % last_upload_basal)
|
||||
|
||||
last_upload_bolus = nightscout.last_uploaded_entry(BOLUS_EVENTTYPE)
|
||||
debug("Nightscout last uploaded bolus: %s" % last_upload_bolus)
|
||||
except Exception as e:
|
||||
log("Error occurred querying Nightscout API:")
|
||||
log(e)
|
||||
errors += 1
|
||||
|
||||
log("-----")
|
||||
|
||||
if errors == 0:
|
||||
print("\nNo API errors returned!")
|
||||
log("No API errors returned!")
|
||||
else:
|
||||
print("\nAPI errors occurred. Please check the errors above.")
|
||||
log("API errors occurred. Please check the errors above.")
|
||||
|
||||
|
||||
with open('tconnectsync-check-output.log', 'w') as f:
|
||||
f.writelines(loglines)
|
||||
|
||||
print("Created file tconnectsync-check-output.log containing additional debugging information.")
|
||||
print("For support, you can upload this file to https://github.com/jwoglom/tconnectsync/issues/new")
|
||||
print("Before uploading, look through the file and remove any sensitive data, such as")
|
||||
print("Nightscout URL and pump serial number.")
|
||||
|
||||
@@ -117,8 +117,9 @@ class TConnectEntry:
|
||||
"bg": data["BG"], # Note: can be empty string for automatic Control-IQ boluses
|
||||
"user_override": data["UserOverride"],
|
||||
"extended_bolus": "1" if extended_bolus else "",
|
||||
"bolex_completion_time": TConnectEntry._datetime_parse(data["BolexCompletionDateTime"]).format() if complete and extended_bolus else None,
|
||||
"bolex_start_time": TConnectEntry._datetime_parse(data["BolexStartDateTime"]).format() if complete and extended_bolus else None,
|
||||
# Note: completion time can be empty if the extended bolus is in progress
|
||||
"bolex_completion_time": TConnectEntry._datetime_parse(data["BolexCompletionDateTime"]).format() if data["BolexCompletionDateTime"] and complete and extended_bolus else None,
|
||||
"bolex_start_time": TConnectEntry._datetime_parse(data["BolexStartDateTime"]).format() if data["BolexStartDateTime"] and complete and extended_bolus else None,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -156,7 +157,10 @@ class TConnectEntry:
|
||||
"alarm": "Empty Cartridge/Pump Shutdown",
|
||||
|
||||
# manual corresponds to a Pumping Suspended by User event
|
||||
"manual": "User Suspended"
|
||||
"manual": "User Suspended",
|
||||
|
||||
# temp-profile corresponds to a Basal Rate Change event to 0u/hr
|
||||
"temp-profile": "Basal Rate Change"
|
||||
}
|
||||
|
||||
BASALSUSPENSION_SKIPPED_EVENTS = {
|
||||
|
||||
+19
-5
@@ -1,10 +1,20 @@
|
||||
import os, sys
|
||||
from dotenv import load_dotenv
|
||||
import os, sys, pathlib
|
||||
from dotenv import dotenv_values
|
||||
|
||||
load_dotenv()
|
||||
cwd_path = os.path.join(os.getcwd(), '.env')
|
||||
global_path = os.path.join(pathlib.Path.home(), '.config/tconnectsync/.env')
|
||||
|
||||
def get(*args):
|
||||
return os.environ.get(*args)
|
||||
values = {}
|
||||
|
||||
if os.path.exists(cwd_path):
|
||||
values = dotenv_values(cwd_path)
|
||||
elif os.path.exists(global_path):
|
||||
values = dotenv_values(global_path)
|
||||
else:
|
||||
values = dotenv_values()
|
||||
|
||||
def get(val, default=None):
|
||||
return os.environ.get(val, values.get(val, default))
|
||||
|
||||
def get_number(name, default):
|
||||
val = get(name, default)
|
||||
@@ -28,6 +38,10 @@ NS_SECRET = get('NS_SECRET', 'apisecret')
|
||||
|
||||
TIMEZONE_NAME = get('TIMEZONE_NAME', 'America/New_York')
|
||||
|
||||
if not get('TIMEZONE_NAME') and get('TZ'):
|
||||
print('TZ environment variable is set, overriding TIMEZONE_NAME')
|
||||
TIMEZONE_NAME = get('TZ')
|
||||
|
||||
# Optional configuration
|
||||
|
||||
AUTOUPDATE_DEFAULT_SLEEP_SECONDS = get_number('AUTOUPDATE_DEFAULT_SLEEP_SECONDS', '300') # 5 minutes
|
||||
|
||||
@@ -594,6 +594,18 @@ class TestTConnectEntryBasalSuspensionEvent(unittest.TestCase):
|
||||
}
|
||||
)
|
||||
|
||||
def test_parse_basalsuspension_event_tempprofile(self):
|
||||
self.assertEqual(
|
||||
TConnectEntry.parse_basalsuspension_event({
|
||||
'EventDateTime': '/Date(1640541521000-0000)/',
|
||||
'SuspendReason': 'temp-profile'
|
||||
}),
|
||||
{
|
||||
"time": "2021-12-26 09:58:41-05:00",
|
||||
"event_type": "Basal Rate Change"
|
||||
}
|
||||
)
|
||||
|
||||
def test_parse_basalsuspension_event_basalprofile_skipped(self):
|
||||
self.assertIsNone(
|
||||
TConnectEntry.parse_basalsuspension_event({
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
import unittest.mock
|
||||
import tempfile
|
||||
import importlib
|
||||
import contextlib
|
||||
import pathlib
|
||||
import os
|
||||
|
||||
@contextlib.contextmanager
|
||||
def chdir(dir):
|
||||
orig_cwd = os.getcwd()
|
||||
os.chdir(dir)
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.chdir(orig_cwd)
|
||||
|
||||
class TestSecretDotEnv(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
if 'TCONNECT_EMAIL' in os.environ:
|
||||
del os.environ['TCONNECT_EMAIL']
|
||||
|
||||
if 'NS_URL' in os.environ:
|
||||
del os.environ['NS_URL']
|
||||
|
||||
def write_test_dotenv_file(self, path, type):
|
||||
with open(os.path.join(path, ".env"), "w") as f:
|
||||
f.write("""
|
||||
TCONNECT_EMAIL=test_%s_email@email.com
|
||||
NS_URL=http://test_%s_url
|
||||
""" % (type, type))
|
||||
f.close()
|
||||
|
||||
def import_secret(self):
|
||||
return importlib.reload(importlib.import_module("tconnectsync.secret"))
|
||||
|
||||
def test_dotenv_in_current_working_directory(self):
|
||||
with tempfile.TemporaryDirectory(prefix='dotenv_cwd') as dir, chdir(dir):
|
||||
self.write_test_dotenv_file(dir, "dotenv_cwd")
|
||||
|
||||
secret = self.import_secret()
|
||||
self.assertEqual(secret.TCONNECT_EMAIL, "test_dotenv_cwd_email@email.com")
|
||||
self.assertEqual(secret.NS_URL, "http://test_dotenv_cwd_url")
|
||||
|
||||
def test_dotenv_in_homedir_config_folder(self):
|
||||
with tempfile.TemporaryDirectory(prefix='dotenv_homedir_config') as dir, chdir(dir):
|
||||
config_dir = os.path.join(dir, '.config/tconnectsync')
|
||||
os.makedirs(config_dir)
|
||||
|
||||
self.write_test_dotenv_file(config_dir, "dotenv_homedir_config")
|
||||
|
||||
with unittest.mock.patch.object(pathlib.Path, "home") as mock_home:
|
||||
mock_home.return_value = dir
|
||||
|
||||
secret = self.import_secret()
|
||||
self.assertEqual(secret.TCONNECT_EMAIL, "test_dotenv_homedir_config_email@email.com")
|
||||
self.assertEqual(secret.NS_URL, "http://test_dotenv_homedir_config_url")
|
||||
|
||||
def test_no_dotenv_file_reads_from_environment(self):
|
||||
with tempfile.TemporaryDirectory(prefix='dotenv_environ') as dir, chdir(dir):
|
||||
environ = {
|
||||
"TCONNECT_EMAIL": "test_environ_email@email.com",
|
||||
"NS_URL": "http://test_environ_url"
|
||||
}
|
||||
|
||||
with unittest.mock.patch.dict(os.environ, environ):
|
||||
secret = self.import_secret()
|
||||
self.assertEqual(secret.TCONNECT_EMAIL, environ["TCONNECT_EMAIL"])
|
||||
self.assertEqual(secret.NS_URL, environ["NS_URL"])
|
||||
|
||||
def test_environment_merges_with_dotenv_file(self):
|
||||
with tempfile.TemporaryDirectory(prefix='env_plus_environ') as dir, chdir(dir):
|
||||
self.write_test_dotenv_file(dir, "dotenv_cwd_defaults")
|
||||
|
||||
environ = {
|
||||
"TCONNECT_EMAIL": "test_environ_override_email@email.com"
|
||||
}
|
||||
|
||||
with unittest.mock.patch.dict(os.environ, environ):
|
||||
secret = self.import_secret()
|
||||
self.assertEqual(secret.TCONNECT_EMAIL, "test_environ_override_email@email.com")
|
||||
self.assertEqual(secret.NS_URL, "http://test_dotenv_cwd_defaults_url")
|
||||
|
||||
def test_dotenv_in_current_working_directory_overrides_homedir_config(self):
|
||||
with tempfile.TemporaryDirectory(prefix='dotenv_cwd') as dir, chdir(dir):
|
||||
self.write_test_dotenv_file(dir, "dotenv_cwd")
|
||||
|
||||
config_dir = os.path.join(dir, '.config/tconnectsync')
|
||||
os.makedirs(config_dir)
|
||||
|
||||
self.write_test_dotenv_file(config_dir, "dotenv_homedir_config")
|
||||
|
||||
with unittest.mock.patch.object(pathlib.Path, "home") as mock_home:
|
||||
mock_home.return_value = dir
|
||||
|
||||
secret = self.import_secret()
|
||||
self.assertEqual(secret.TCONNECT_EMAIL, "test_dotenv_cwd_email@email.com")
|
||||
self.assertEqual(secret.NS_URL, "http://test_dotenv_cwd_url")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user