From 3bbc709f8582295b52e5ad40531d5d42554cbc51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20W=C3=B3jtowicz?= Date: Sat, 23 Nov 2024 12:57:34 +0100 Subject: [PATCH] 2.x --- .github/FUNDING.yml | 1 + CODE_OF_CONDUCT.md | 128 +++++ LICENSE | 674 +++++++++++++++++++++++++++ README.md | 130 ++++++ import_data.sh | 251 ++++++++++ manuals/Miscale_BLE.md | 159 +++++++ manuals/Miscale_ESP32.jpg | Bin 0 -> 80316 bytes manuals/Miscale_ESP32.md | 169 +++++++ manuals/Miscale_ESP32_win.md | 52 +++ manuals/Omron_BLE.md | 133 ++++++ manuals/all_BLE_win.md | 95 ++++ manuals/usb.jpg | Bin 0 -> 83587 bytes manuals/workflow.png | Bin 0 -> 91394 bytes miscale/Xiaomi_Scale_Body_Metrics.py | 230 +++++++++ miscale/body_scales.py | 157 +++++++ miscale/miscale_ble.py | 148 ++++++ miscale/miscale_esp32.ino | 215 +++++++++ miscale/miscale_export.py | 90 ++++ omron/deviceSpecific/hem-6232t.py | 59 +++ omron/deviceSpecific/hem-7150t.py | 56 +++ omron/deviceSpecific/hem-7155t.py | 56 +++ omron/deviceSpecific/hem-7322t.py | 57 +++ omron/deviceSpecific/hem-7342t.py | 56 +++ omron/deviceSpecific/hem-7361t.py | 56 +++ omron/deviceSpecific/hem-7530t.py | 40 ++ omron/deviceSpecific/hem-7600t.py | 57 +++ omron/omblepy.py | 397 ++++++++++++++++ omron/omron_export.py | 74 +++ omron/omron_pairing.sh | 33 ++ omron/sharedDriver.py | 138 ++++++ user/export2garmin.cfg | 81 ++++ user/import_tokens.py | 46 ++ 32 files changed, 3838 insertions(+) create mode 100644 .github/FUNDING.yml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 import_data.sh create mode 100644 manuals/Miscale_BLE.md create mode 100644 manuals/Miscale_ESP32.jpg create mode 100644 manuals/Miscale_ESP32.md create mode 100644 manuals/Miscale_ESP32_win.md create mode 100644 manuals/Omron_BLE.md create mode 100644 manuals/all_BLE_win.md create mode 100644 manuals/usb.jpg create mode 100644 manuals/workflow.png create mode 100644 miscale/Xiaomi_Scale_Body_Metrics.py create mode 100644 miscale/body_scales.py create mode 100644 miscale/miscale_ble.py create mode 100644 miscale/miscale_esp32.ino create mode 100644 miscale/miscale_export.py create mode 100644 omron/deviceSpecific/hem-6232t.py create mode 100644 omron/deviceSpecific/hem-7150t.py create mode 100644 omron/deviceSpecific/hem-7155t.py create mode 100644 omron/deviceSpecific/hem-7322t.py create mode 100644 omron/deviceSpecific/hem-7342t.py create mode 100644 omron/deviceSpecific/hem-7361t.py create mode 100644 omron/deviceSpecific/hem-7530t.py create mode 100644 omron/deviceSpecific/hem-7600t.py create mode 100644 omron/omblepy.py create mode 100644 omron/omron_export.py create mode 100644 omron/omron_pairing.sh create mode 100644 omron/sharedDriver.py create mode 100644 user/export2garmin.cfg create mode 100644 user/import_tokens.py diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..97361d1 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +buy_me_a_coffee: RobertWojtowicz \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4f819e3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +rkwojtowicz@gmail.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e72bfdd --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a9b0e79 --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +# Export 2 to Garmin Connect + +## 1. Introduction +- This project is based on following projects: + - https://github.com/cyberjunky/python-garminconnect; + - https://github.com/wiecosystem/Bluetooth; + - https://github.com/userx14/omblepy; + - https://github.com/lolouk44/xiaomi_mi_scale; + - https://github.com/dorssel/usbipd-win; + - https://github.com/rando-calrissian/esp32_xiaomi_mi_2_hass. + +### 1.1. Miscale module (once known as miscale2garmin): +- Allows fully automatic synchronization of Mi Body Composition Scale 2 (tested on XMTZC05HM) directly to Garmin Connect, with following parameters: + - Date and Time (measurement, from device); + - Weight (**_NOTE:_ lbs is automatically converted to kg**); + - BMI (Body Mass Index); + - Body Fat; + - Skeletal Muscle Mass; + - Bone Mass; + - Body Water; + - Physique Rating; + - Visceral Fat; + - Metabolic Age. +- Miscale_backup.csv file also contains other parameters (can be imported e.g. for analysis into Excel): + - BMR (Basal Metabolic Rate); + - LBM (Lean Body Mass); + - Ideal Weight; + - Fat Mass To Ideal; + - Protein; + - Data Status (to_import, failed, uploaded); + - Unix Time (based on Date and Time); + - Email User (used account for Garmin Connect); + - Upload Date and Upload Time (to Garmin Connect); + - Difference Time (between measuring and uploading); + - Battery status in V and % (ESP32 only). +- Supports multiple users with individual weights ranges, we can link multiple accounts with Garmin Connect. + +### 1.2. Omron module: +- Allows fully automatic synchronization of Omron blood pressure (tested on M4/HEM-7155T and M7/HEM-7322T Intelli IT) directly to Garmin Connect, with following parameters: + - Date and Time (measurement, from device); + - DIAstolic blood pressure; + - SYStolic blood pressure; + - Heart rate. +- Omron_backup.csv file also contains other parameters (can be imported e.g. for analysis into Excel): + - Category (**_NOTE:_ EU and US classification only**); + - MOV (Movement detection); + - IHB (Irregular Heart Beat); + - Data Status (to_import, failed, uploaded); + - Unix Time (based on Date and Time); + - Email User (used account for Garmin Connect); + - Upload Date and Upload Time (to Garmin Connect); + - Difference Time (between measuring and uploading). +- Supports 2 users from Omron device, we can connect 2 accounts with Garmin Connect. + +### 1.3. User module: +- Enables configuration of all parameters related to integration Miscale and Omron; +- Provides export Oauth1 and Oauth2 tokens of your account from Garmin Connect (MFA/2FA support). + +## 2. How does this work +- Miscale and Omron modules can be activated individually or run together: +``` +$ /home/robert/export2garmin-master/import_data.sh + +============================================= +Export 2 Garmin Connect v2.2 (import_data.sh) +============================================= + +18.07.2024-16:56:01 SYSTEM * BLE adapter enabled in export2garmin.cfg, check if available +18.07.2024-16:56:01 SYSTEM * BLE adapter hci0(00:00:00:00:00:00) working, check if temp.log exists +18.07.2024-16:56:01 SYSTEM * temp.log file exists, go to modules +18.07.2024-16:56:01 MISCALE * Module is on +18.07.2024-16:56:01 MISCALE * miscale_backup.csv file exists, checking for new data +18.07.2024-16:56:01 MISCALE * Importing data from a BLE adapter +18.07.2024-16:56:06 MISCALE * Saving import 1721314589 to miscale_backup.csv file +18.07.2024-16:56:07 MISCALE * Calculating data from import 1721314589, upload to Garmin Connect +18.07.2024-16:56:07 MISCALE * Data upload to Garmin Connect is complete +18.07.2024-16:56:07 MISCALE * Saving calculated data from import 1721314589 to miscale_backup.csv file +18.07.2024-16:56:07 OMRON * Module is on +18.07.2024-16:56:07 OMRON * omron_backup.csv file exists, checking for new data +18.07.2024-16:56:07 OMRON * Importing data from a BLE adapter +18.07.2024-16:56:39 OMRON * Prepare data for omron_backup.csv file +18.07.2024-16:56:40 OMRON * Calculating data from import 1721314552, upload to Garmin Connect +18.07.2024-16:56:40 OMRON * Data upload to Garmin Connect is complete +18.07.2024-16:56:40 OMRON * Saving calculated data from import 1721314552 to omron_backup.csv file +``` +- Synchronization diagram from Export 2 to Garmin Connect: + +![alt text](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/workflow.png) + +### 2.1. Miscale module | BLE VERSION +- After weighing, Mi Body Composition Scale 2 is active for 15 minutes on bluetooth transmission; +- USB Bluetooth adapter or internal module scans BLE devices for 10 seconds to acquire data from scale; +- Body weight and impedance data on server are appropriately processed by scripts; +- Processed data are sent to Garmin Connect; +- Raw and calculated data from scale is backed up on server in miscale_backup.csv file. + +**Select your platform and go to instructions:** +- [Debian 12 | Raspberry Pi OS (based on Debian 12)](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/Miscale_BLE.md); +- [Windows 11](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/all_BLE_win.md). + +### 2.2. Miscale module | ESP32 VERSION +- After weighing, Mi Body Composition Scale 2 is active for 15 minutes on bluetooth transmission; +- ESP32 module operates in a deep sleep and wakes up every 7 minutes, scans BLE devices for 10 seconds to acquire data from scale, process can be started immediately via reset button; +- ESP32 module sends acquired data via MQTT protocol to MQTT broker installed on server; +- Body weight and impedance data on server are appropriately processed by scripts; +- Processed data are sent to Garmin Connect; +- Raw and calculated data from scale is backed up on server in miscale_backup.csv file. + +**Select your platform and go to instructions:** +- [Debian 12 | Raspberry Pi OS (based on Debian 12)](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/Miscale_ESP32.md); +- [Windows 11](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/Miscale_ESP32_win.md). + +### 2.3. Omron module | BLE VERSION +- After measuring blood pressure, Omron allows you to download measurement data once; +- USB bluetooth adapter or internal module scans BLE devices for 10 seconds to acquire data from blood pressure device (downloading data can take about 1 minute); +- Pressure measurement data are appropriately processed by scripts on server; +- Processed data are sent to Garmin Connect; +- Raw and calculated data from device is backed up on server in omron_backup.csv file. + +**Select your platform and go to instructions:** +- [Debian 12 | Raspberry Pi OS (based on Debian 12)](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/Omron_BLE.md); +- [Windows 11](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/all_BLE_win.md). + +## 3. Mobile App +I don't plan to create a mobile app, but I encourage you to take advantage of another projects (applies to Mi Body Composition Scale / Mi Scale support only): +- Android: https://github.com/lswiderski/mi-scale-exporter; +- iOS | iPadOS: https://github.com/lswiderski/WebBodyComposition. + +## If you like my work, you can buy me a coffee +Buy Me A Coffee \ No newline at end of file diff --git a/import_data.sh b/import_data.sh new file mode 100644 index 0000000..1a714ef --- /dev/null +++ b/import_data.sh @@ -0,0 +1,251 @@ +#!/bin/bash + +# Version Info +echo -e "\n=============================================" +echo -e "Export 2 Garmin Connect v2.2 (import_data.sh)" +echo -e "=============================================\n" + +# Blocking multiple instances of same script process +timenow() { + date +%d.%m.%Y-%H:%M:%S +} +remove_lock() { + rm -f "/dev/shm/export.lock" +} +another_instance() { + echo "$(timenow) SYSTEM * Another import_data.sh instance running" + exit 1 +} +lockfile -r 0 -l 60 "/dev/shm/export.lock" || another_instance +trap remove_lock EXIT + +# Create a loop, "-l" parameter executes loop indefinitely +path=$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd) +source <(grep switch_ $path/user/export2garmin.cfg) +loop_count=1 +found_count=0 +[[ $1 == "-l" ]] && loop_count=0 +i=0 +while [[ $loop_count -eq 0 ]] || [[ $i -lt $loop_count ]] ; do + ((i++)) + + # Verifying correct working of BLE, restart bluetooth service and device via miscale_ble.py + if [[ $switch_miscale == "on" && $switch_mqtt == "off" ]] || [[ $switch_omron == "on" ]] ; then + unset $(compgen -v | grep '^ble_') + echo "$(timenow) SYSTEM * BLE adapter enabled in export2garmin.cfg, check if available" + ble_check=$(python3 -B $path/miscale/miscale_ble.py) + if echo $ble_check | grep -q "failed" ; then + echo "$(timenow) SYSTEM * BLE adapter not working, skip scanning check if temp.log exists" + else ble_status=ok + hci_mac=$(echo $ble_check | grep -o 'h.\{21\})' | head -n 1) + echo "$(timenow) SYSTEM * BLE adapter $hci_mac working, check if temp.log exists" + fi + else echo "$(timenow) SYSTEM * BLE adapter not enabled or incorrect configuration in export2garmin.cfg, check if temp.log exists" + fi + + # Create temp.log file if it exists cleanup after last startup + if [[ $switch_miscale == "on" ]] || [[ $switch_omron == "on" ]] ; then + if [[ ! -f /dev/shm/temp.log ]] ; then + echo "$(timenow) SYSTEM * Creating temp.log file, go to modules" + echo > /dev/shm/temp.log + else echo "$(timenow) SYSTEM * temp.log file exists, go to modules" + > /dev/shm/temp.log + fi + fi + + # Mi Body Composition Scale 2 + if [[ $switch_miscale == "on" ]] ; then + miscale_backup=$path/user/miscale_backup.csv + echo "$(timenow) MISCALE * Module is on" + + # Creating miscale_backup.csv file + if [[ ! -f $miscale_backup ]] ; then + miscale_header="Data Status;Unix Time;Date [dd.mm.yyyy];Time [hh:mm];Weight [kg];Change [kg];BMI;Body Fat [%];Skeletal Muscle Mass [kg];Bone Mass [kg];Body Water [%];Physique Rating;Visceral Fat;Metabolic Age [years];BMR [kCal];LBM [kg];Ideal Wieght [kg];Fat Mass To Ideal [type:mass kg];Protein [%];Impedance;Email User;Upload Date [dd.mm.yyyy];Upload Time [hh:mm];Difference Time [s]" + [[ $switch_mqtt == "on" ]] && miscale_header="$miscale_header;Battery [V];Battery [%]" + echo "$(timenow) MISCALE * Creating miscale_backup.csv file, checking for new data" + echo $miscale_header > $miscale_backup + else echo "$(timenow) MISCALE * miscale_backup.csv file exists, checking for new data" + fi + + # Importing raw data from source (BLE or MQTT) + if [[ $switch_mqtt == "on" ]] ; then + source <(grep miscale_mqtt_ $path/user/export2garmin.cfg) + echo "$(timenow) MISCALE * Importing data from an MQTT broker" + miscale_read=$(mosquitto_sub -h localhost -t 'data' -u "$miscale_mqtt_user" -P "$miscale_mqtt_passwd" -C 1 -W 10) + miscale_unixtime=$(echo $miscale_read | cut -d ";" -f 1) + if [[ -z $miscale_unixtime ]] ; then + echo "$(timenow) MISCALE * No MQTT data, check connection to MQTT broker or ESP32" + fi + elif [[ $ble_status == "ok" ]] ; then + echo "$(timenow) MISCALE * Importing data from a BLE adapter" + if echo $ble_check | grep -q "incomplete" ; then + echo "$(timenow) MISCALE * Reading BLE data incomplete, repeat weighing" + else miscale_read=$(echo $ble_check | awk '{sub(/.*BLE scan/, ""); print substr($1,1)}') + miscale_unixtime=$(echo $miscale_read | cut -d ";" -f 1) + fi + fi + + # Check time synchronization between scale and OS + if [[ -n $miscale_unixtime ]] ; then + source <(grep miscale_time_ $path/user/export2garmin.cfg) + miscale_os_unixtime=$(date +%s) + miscale_time_zone=$(date +%z | cut -c1-3) + miscale_offset_unixtime=$(( $miscale_unixtime + $miscale_time_zone * 3600 + $miscale_time_offset )) + miscale_time_shift=$(( $miscale_os_unixtime - $miscale_offset_unixtime )) + miscale_absolute_shift=${miscale_time_shift#-} + if (( $miscale_absolute_shift < $miscale_time_unsync )) ; then + miscale_found_entry=false + + # Check for duplicates, similar raw data in miscale_backup.csv file + while IFS=";" read -r _ unix_time _ ; do + if [[ $unix_time =~ ^[0-9]+$ ]] ; then + miscale_time_dif=$(($miscale_offset_unixtime - $unix_time)) + miscale_time_dif=${miscale_time_dif#-} + if (( $miscale_time_dif < $miscale_time_check )) ; then + miscale_found_entry=true + break + fi + fi + done < $miscale_backup + + # Save raw data to miscale_backup.csv file + if [[ $miscale_found_entry == "false" ]] ; then + echo "$(timenow) MISCALE * Saving import $miscale_offset_unixtime to miscale_backup.csv file" + miscale_offset_row=${miscale_read/${miscale_unixtime}/to_import;${miscale_offset_unixtime}} + echo $miscale_offset_row >> $miscale_backup + else echo "$(timenow) MISCALE * $miscale_time_dif s time difference, same or similar data already exists in miscale_backup.csv file" + fi + else echo "$(timenow) MISCALE * $miscale_time_shift s time difference, synchronize date and time scale" + echo "$(timenow) MISCALE * Time offset is set to $miscale_offset s" + fi + fi + + # Calculating data and upload to Garmin Connect, print to temp.log file + if grep -q "failed\|to_import" $miscale_backup ; then + python3 -B $path/miscale/miscale_export.py > /dev/shm/temp.log 2>&1 + miscale_import=$(awk -F ": " '/MISCALE /*/ Import data:/{print substr($2,1,10)}' /dev/shm/temp.log) + echo "$(timenow) MISCALE * Calculating data from import $miscale_import, upload to Garmin Connect" + fi + + # Handling errors from temp.log file + if [[ -z $miscale_import ]] ; then + echo "$(timenow) MISCALE * There is no new data to upload to Garmin Connect" + elif grep -q "MISCALE \* There" /dev/shm/temp.log ; then + echo "$(timenow) MISCALE * There is no user with given weight or undefined user email@email.com, check users section in export2garmin.cfg" + echo "$(timenow) MISCALE * Deleting import $miscale_import from miscale_backup.csv file" + sed -i "/$miscale_import/d" $miscale_backup + elif grep -q "Err" /dev/shm/temp.log ; then + echo "$(timenow) MISCALE * Upload to Garmin Connect has failed, check temp.log for error details" + sed -i "s/to_import;$miscale_import/failed;$miscale_import/" $miscale_backup + else echo "$(timenow) MISCALE * Data upload to Garmin Connect is complete" + + # Save calculated data to miscale_backup.csv file + echo "$(timenow) MISCALE * Saving calculated data from import $miscale_import to miscale_backup.csv file" + miscale_import_data=$(awk -F ": " '/MISCALE /*/ Import data:/{print $2}' /dev/shm/temp.log) + miscale_calc_data=$(awk -F ": " '/MISCALE /*/ Calculated data:/{print $2}' /dev/shm/temp.log) + miscale_import_diff=$(echo $miscale_calc_data | cut -d ";" -f 1-3) + miscale_check_line=$(wc -l < $miscale_backup) + sed -i "s/failed;$miscale_import_data/uploaded;$miscale_import;$miscale_calc_data;$miscale_time_shift/; s/to_import;$miscale_import_data/uploaded;$miscale_import;$miscale_calc_data;$miscale_time_shift/" $miscale_backup + if [[ $miscale_check_line == "2" ]] ; then + sed -i "s/$miscale_import;$miscale_import_diff/$miscale_import;$miscale_import_diff;0.0/" $miscale_backup + else miscale_email_user=$(echo $miscale_calc_data | cut -d ";" -f 18) + miscale_weight_last=$(grep $miscale_email_user $miscale_backup | sed -n 'x;$p' | cut -d ";" -f 5) + miscale_weight_import=$(echo $miscale_calc_data | cut -d ";" -f 3) + miscale_weight_diff=$(echo $miscale_weight_import - $miscale_weight_last | bc | sed "s/^-\./-0./; s/^\./0./") + sed -i "s/$miscale_import;$miscale_import_diff/$miscale_import;$miscale_import_diff;$miscale_weight_diff/; s/;0;/;0.0;/" $miscale_backup + fi + fi + unset $(compgen -v | grep '^miscale_') + else echo "$(timenow) MISCALE * Module is off" + fi + + # Omron blood pressure + if [[ $switch_omron == "on" ]] ; then + omron_backup=$path/user/omron_backup.csv + echo "$(timenow) OMRON * Module is on" + + # Creating omron_backup.csv file + if [[ ! -f $omron_backup ]] ; then + echo "Data Status;Unix Time;Date [dd.mm.yyyy];Time [hh:mm];SYStolic [mmHg];DIAstolic [mmHg];Heart Rate [bpm];Category;MOV;IHB;Email User;Upload Date [dd.mm.yyyy];Upload Time [hh:mm];Difference Time [s]" > $omron_backup + echo "$(timenow) OMRON * Creating omron_backup.csv file, checking for new data" + else echo "$(timenow) OMRON * omron_backup.csv file exists, checking for new data" + fi + + # Importing raw data from source (BLE) + if [[ $ble_status == "ok" ]] ; then + echo "$(timenow) OMRON * Importing data from a BLE adapter" + coproc ble { bluetoothctl; } + while true ; do + source <(grep omron_omblepy_ $path/user/export2garmin.cfg) + omron_hci=$(echo $ble_check | grep -o 'hci.' | head -n 1) + omron_omblepy_check=$(timeout ${omron_omblepy_time}s python3 -B $path/omron/omblepy.py -a $omron_hci -p -d $omron_omblepy_model 2> /dev/null) + if echo $omron_omblepy_check | grep -q $omron_omblepy_mac ; then + + # Adding an exception for selected models + if [[ $omron_omblepy_model == "hem-6232t" ]] || [[ $omron_omblepy_model == "hem-7530t" ]] ; then + omron_omblepy_flags="-n" + else omron_omblepy_flags="-n -t" + fi + if [[ $omron_omblepy_debug == "on" ]] ; then + python3 -B $path/omron/omblepy.py -a $omron_hci -d $omron_omblepy_model --loggerDebug -m $omron_omblepy_mac + elif [[ $omron_omblepy_all == "on" ]] ; then + python3 -B $path/omron/omblepy.py -a $omron_hci -d $omron_omblepy_model -m $omron_omblepy_mac >/dev/null 2>&1 + else python3 -B $path/omron/omblepy.py $omron_omblepy_flags -a $omron_hci -d $omron_omblepy_model -m $omron_omblepy_mac >/dev/null 2>&1 + fi + else exec {ble[0]}>&- + exec {ble[1]}>&- + wait $ble_PID + break + fi + done + if [[ -f "/dev/shm/omron_user1.csv" ]] || [[ -f "/dev/shm/omron_user2.csv" ]] ; then + source <(grep omron_export_user $path/user/export2garmin.cfg) + echo "$(timenow) OMRON * Prepare data for omron_backup.csv file" + awk -F ';' 'NR==FNR{a[$2];next}!($2 in a)' $omron_backup /dev/shm/omron_user1.csv > /dev/shm/omron_users.csv + awk -F ';' 'NR==FNR{a[$2];next}!($2 in a)' $omron_backup /dev/shm/omron_user2.csv >> /dev/shm/omron_users.csv + sed -i "s/ /;/g; s/user1/$omron_export_user1/; s/user2/$omron_export_user2/" /dev/shm/omron_users.csv + grep -q "email@email.com" /dev/shm/omron_users.csv && echo "$(timenow) OMRON * Deleting records with undefined user email@email.com, check users section in export2garmin.cfg" && sed -i "/email@email\.com/d" /dev/shm/omron_users.csv + cat /dev/shm/omron_users.csv >> $omron_backup + rm /dev/shm/omron_user*.csv + fi + fi + + # Upload to Garmin Connect, print to temp.log file + if grep -q "failed\|to_import" $omron_backup ; then + if [[ $switch_miscale == "on" ]] ; then + python3 -B $path/omron/omron_export.py >> /dev/shm/temp.log 2>&1 + else python3 -B $path/omron/omron_export.py > /dev/shm/temp.log 2>&1 + fi + omron_import=$(awk -F ": " '/OMRON /*/ Import data:/{print substr($2,1,10)}' /dev/shm/temp.log) + echo "$(timenow) OMRON * Calculating data from import $omron_import, upload to Garmin Connect" + fi + + # Handling errors, save data to omron_backup.csv file + if [[ -z $omron_import ]] ; then + echo "$(timenow) OMRON * There is no new data to upload to Garmin Connect" + else omron_import_data=$(awk -F ": " '/OMRON /*/ Import data:/{print $2}' /dev/shm/temp.log) + omron_cut_data=$(echo $omron_import_data | cut -d ";" -f 1-6) + omron_calc_data=$(awk -F ": " '/OMRON /*/ Calculated data:/{print $2}' /dev/shm/temp.log) + omron_os_unixtime=$(date +%s) + omron_time_shift=$(( $omron_os_unixtime - $omron_import )) + if grep -q "Err" /dev/shm/temp.log ; then + if grep -q "MISCALE \* Upload" /dev/shm/temp.log ; then + echo "$(timenow) OMRON * Upload to Garmin Connect has failed, check temp.log for error details" + sed -i "s/to_import;$omron_import/failed;$omron_import/" $omron_backup + elif grep -q "OMRON \* Upload" /dev/shm/temp.log ; then + echo "$(timenow) OMRON * Data upload to Garmin Connect is complete" + echo "$(timenow) OMRON * Saving calculated data from import $omron_import to omron_backup.csv file" + sed -i "s/failed;$omron_import_data/uploaded;omron_cut_data;$omron_calc_data;$omron_time_shift/; s/to_import;$omron_import_data/uploaded;$omron_cut_data;$omron_calc_data;$omron_time_shift/" $omron_backup + else echo "$(timenow) OMRON * Upload to Garmin Connect has failed, check temp.log for error details" + sed -i "s/to_import;$omron_import/failed;$omron_import/" $omron_backup + fi + else echo "$(timenow) OMRON * Data upload to Garmin Connect is complete" + echo "$(timenow) OMRON * Saving calculated data from import $omron_import to omron_backup.csv file" + sed -i "s/failed;$omron_import_data/uploaded;omron_cut_data;$omron_calc_data;$omron_time_shift/; s/to_import;$omron_import_data/uploaded;$omron_cut_data;$omron_calc_data;$omron_time_shift/" $omron_backup + fi + fi + unset $(compgen -v | grep '^omron_') + else echo "$(timenow) OMRON * Module is off" + fi + [[ $loop_count -eq 1 ]] && break +done \ No newline at end of file diff --git a/manuals/Miscale_BLE.md b/manuals/Miscale_BLE.md new file mode 100644 index 0000000..6755643 --- /dev/null +++ b/manuals/Miscale_BLE.md @@ -0,0 +1,159 @@ +## 2.1. Miscale_BLE VERSION + +### 2.1.1. Getting MAC address of Mi Body Composition Scale 2 / disable weigh small object +- Install Zepp Life App on your mobile device from Play Store; +- Configure your scale with Zepp Life App on your mobile device (tested on Android 10-14); +- Retrieve scale's MAC address from Zepp Life App (Profile > My devices > Mi Body Composition Scale 2); +- Turn off weigh small object in Zepp Life App (Profile > My devices > Mi Body Composition Scale 2) for better measurement quality. + +### 2.1.2. Setting correct date and time in Mi Body Composition Scale 2 +- Launch Zepp Life App, go to scale (Profile > My devices > Mi Body Composition Scale 2); +- Start scale and select Clear data in App; +- Take a new weight measurement with App, App should synchronize date and time (UTC); +- You should also synchronize scale after replacing batteries. + +### 2.1.3. Preparing operating system +- Minimum hardware and software requirements are: + - x86: 1vCPU, 1024MB RAM, 8GB disk space, network connection, Debian 12 operating system; + - ARM: 1CPU, 512MB RAM, 8GB disk space, network connection, Raspberry Pi OS (based on Debian 12) | Debian 12 operating system; + - In some cases of Raspberry Pi when using internal WiFi and bluetooth, you should connect internal WiFi on **_5GHz_**, because on 2,4GHz there may be a problem with connection stability (sharing same antenna). +- Update your system and then install following packages: +``` +$ sudo apt update && sudo apt full-upgrade -y && sudo apt install -y wget python3 bc bluetooth python3-pip libglib2.0-dev procmail +$ sudo pip3 install --upgrade bluepy garminconnect --break-system-packages +``` +- Modify file `sudo nano /etc/systemd/system/bluetooth.target.wants/bluetooth.service`: +``` +ExecStart=/usr/libexec/bluetooth/bluetoothd --experimental +``` +- Download and extract to your home directory (e.g. "/home/robert/"), make a files executable: +``` +$ wget https://github.com/RobertWojtowicz/export2garmin/archive/refs/heads/master.tar.gz -O - | tar -xz +$ cd export2garmin-master && sudo chmod 755 import_data.sh && sudo chmod 555 /etc/bluetooth +$ sudo setcap 'cap_net_raw,cap_net_admin+eip' /usr/local/lib/python3.11/dist-packages/bluepy/bluepy-helper +``` + +### 2.1.4. Configuring scripts +- First script is `user/import_tokens.py` is used to export Oauth1 and Oauth2 tokens of your account from Garmin Connect; + - Script has support for login with or without MFA; + - Once a year, tokens must be exported again, due to their expiration; + - When you run `user/import_tokens.py`, you need to provide a login and password and possibly a code from MFA: + ``` + $ python3 /home/robert/export2garmin-master/user/import_tokens.py + + =============================================== + Export 2 Garmin Connect v2.0 (import_tokens.py) + =============================================== + + 28.04.2024-11:58:44 * Login e-mail: email@email.com + 28.04.2024-11:58:50 * Enter password: + 28.04.2024-11:58:57 * MFA/2FA one-time code: 000000 + 28.04.2024-11:59:17 * Oauth tokens saved correctly + ``` +- Configuration is stored in `user/export2garmin.cfg` file (make changes e.g. via `sudo nano`): + - Complete data in "miscale_export_user*" parameter sex, height in cm, birthdate in dd-mm-yyyy and login e-mail to Garmin Connect, max_weight in kg, min_weight in kg; + - To enable Miscale module, set "on" in "switch_miscale" parameter; + - Complete data in "ble_miscale_mac" parameter, which is related to MAC address of scale, if you don't know MAC address read section [2.1.1.](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/Miscale_BLE.md#211-getting-mac-address-of-mi-body-composition-scale-2--disable-weigh-small-object); + - Configuration file contains many other options, check descriptions and use for your configuration. +- Second script `miscale/miscale_ble.py` has implemented debug mode, you can verify if everything is working properly, just execute it from console: +``` +$ python3 /home/robert/export2garmin-master/miscale/miscale_ble.py + +============================================= +Export 2 Garmin Connect v2.2 (miscale_ble.py) +============================================= + +18.11.2023-23:23:30 * Checking if a BLE adapter is detected +18.11.2023-23:23:30 * BLE adapter hci0(00:00:00:00:00:00) detected, check BLE adapter connection +18.11.2023-23:23:30 * Connection to BLE adapter hci0(00:00:00:00:00:00) works, starting BLE scan: + BLE device found with address: 3f:f1:3e:a6:4d:00, non-target device + BLE device found with address: 42:db:e4:c4:5c:d4, non-target device + BLE device found with address: 24:fc:e5:8f:ce:bf, non-target device + BLE device found with address: 00:00:00:00:00:00 <= target device +18.11.2023-23:23:34 * Reading BLE data complete, finished BLE scan +1672412076;58.4;521 +``` +- Third script "import_data.sh" has implemented debug mode, you can verify if everything is working properly, just execute it from console: +``` +$ /home/robert/export2garmin-master/import_data.sh + +============================================= +Export 2 Garmin Connect v2.2 (import_data.sh) +============================================= + +15.07.2024-23:00:11 SYSTEM * BLE adapter enabled in export2garmin.cfg, check if available +15.07.2024-23:00:11 SYSTEM * BLE adapter hci0(00:00:00:00:00:00) working, check if temp.log exists +15.07.2024-23:00:11 SYSTEM * temp.log file exists, go to modules +15.07.2024-23:00:11 MISCALE * Module is on +15.07.2024-23:00:11 MISCALE * miscale_backup.csv file exists, checking for new data +15.07.2024-23:00:11 MISCALE * Importing data from a BLE adapter +15.07.2024-23:00:12 MISCALE * Saving import 1721076654 to miscale_backup.csv file +15.07.2024-23:00:16 MISCALE * Calculating data from import 1721076654, upload to Garmin Connect +15.07.2024-23:00:16 MISCALE * Data upload to Garmin Connect is complete +15.07.2024-23:00:16 MISCALE * Saving calculated data from import 1721076654 to miscale_backup.csv file +15.07.2024-23:00:16 OMRON * Module is off +``` +- If there is an error upload to Garmin Connect, data will be sent again on next execution, upload errors and other operations are saved in temp.log file: +``` +$ cat /dev/shm/temp.log + +=============================================== +Export 2 Garmin Connect v2.0 (miscale_export.py +=============================================== + +MISCALE * Import data: 1721076654;55.2;508 +MISCALE * Calculated data: 15.07.2024;22:50;55.2;18.7;10.8;46.7;2.6;61.2;7;4;19;1217;51.1;64.4;to_gain:6.8;23.4;508;email@email.com;15.07.2024;23:00 +MISCALE * Upload status: OK +``` +- Finally, if everything works correctly add script import_data.sh as a service, make sure about path: +``` +$ find / -name import_data.sh +/home/robert/export2garmin-master/import_data.sh +``` +- To run it at system startup in an infinite loop, create a file `sudo nano /etc/systemd/system/export2garmin.service` enter previously searched path to import_data.sh and include "User" name: +``` +[Unit] +Description=Export2Garmin service +After=network.target + +[Service] +Type=simple +User=robert +ExecStart=/home/robert/export2garmin-master/import_data.sh -l +Restart=on-failure + +[Install] +WantedBy=multi-user.target +``` +- Activate Export2Garmin service and run it: +``` +sudo systemctl enable export2garmin.service && sudo systemctl start export2garmin.service +``` +- You can check if export2garmin service works `sudo systemctl status export2garmin.service` or temporarily stop it with command `sudo systemctl stop export2garmin.service`. + +### 2.1.5. How to increase BLE range +- Purchase a cheap USB bluetooth: + - 5.0/5.1 (tested on RTL8761B chipset, manufacturer Zexmte, works with Miscale and Omron module); + - 5.3 (tested on ATS2851 chipset, manufacturer Zexmte, works only with Miscale module). +- Bluetooth adapter should have a removable RP-SMA antenna; +- You will have option to change if standard RP-SMA antenna included with bluetooth adapter gives too little range; +- Sometimes if you increase antenna range, scan time is too short to find your scale (too many devices around), you should increase scan_time parameter in scanner_ble.py script; +- ATS2851 chipset has native support in Debian 12 operating system | Raspberry Pi OS (based on Debian 12) no additional driver needed; +- If you are using a virtual machine add USB chipset using passthrough mechanism (for performance and stability), connect USB bluetooth adapter; +- Assign USB chipset to virtual machine from hypevisor (tested on VMware ESXi 7/8); +- RTL8761B chipset requires driver (for Raspberry Pi OS skip this step), install Realtek package and restart virtual machine: +``` +sudo apt install -y firmware-realtek +sudo reboot +``` +- If you are using multiple BLE adapters, select appropriate one by HCI number or MAC address (recommended) and set in `user/export2garmin.cfg` file; +- Use command `sudo hciconfig -a` to locate BLE adapter, and then select type of identification: + - By HCI number, set parameter "ble_adapter_hci"; + - By MAC address, set parameter "ble_adapter_switch" to "on" and specify MAC addres in parameter "ble_adapter_mac". +- Sample photo with test configuration, on left Raspberry Pi 0W, on right server with virtual machine (stronger antenna added): + +![alt text](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/usb.jpg) +- Back to [README](https://github.com/RobertWojtowicz/export2garmin/blob/master/README.md). + +## If you like my work, you can buy me a coffee +Buy Me A Coffee \ No newline at end of file diff --git a/manuals/Miscale_ESP32.jpg b/manuals/Miscale_ESP32.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2314d593ffbd1fb6a7c2e5e8e3aada096d0b9ae8 GIT binary patch literal 80316 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 c@d7BOUF7oBpd1uFh31U~!Sz-qi}| My devices > Mi Body Composition Scale 2); +- Turn off weigh small object in Zepp Life App (Profile > My devices > Mi Body Composition Scale 2) for better measurement quality. + +### 2.2.2. Setting correct date and time in Mi Body Composition Scale 2 +- Launch Zepp Life App, go to scale (Profile > My devices > Mi Body Composition Scale 2); +- Start scale and select Clear data in App; +- Take a new weight measurement with App, App should synchronize date and time (UTC); +- You should also synchronize scale after replacing batteries. + +### 2.2.3. ESP32 configuration (bluetooth gateway to WiFi/MQTT) +- Use Arduino IDE to compile and upload software to ESP32, following board and libraries required: + - Arduino ESP32: https://github.com/espressif/arduino-esp32; + - Battery 18650 Stats: https://github.com/danilopinotti/Battery18650Stats; + - PubSubClient: https://github.com/knolleary/pubsubclient; + - Timestamps: https://github.com/alve89/Timestamps. +- How to install board and library in Arduino IDE?: + - board (**_NOTE:_ use version 1.0.4, newer is unstable**): https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html; + - libraries: https://www.arduino.cc/en/Guide/Libraries. +- Preparing Arduino IDE to upload project to ESP32, go to Tools and select: + - Board: > ESP32 Arduino > "WEMOS LOLIN32"; + - Upload Speed: "921600"; + - CPU Frequency: > "80MHz (WiFi / BT)" for better energy saving; + - Flash Frequency: "80Mhz"; + - Partition Scheme: > "No OTA (Large APP)"; + - Port: > "COM" on which ESP32 board is detected. +- Following information must be entered before compiling code (esp32.ino) in Arduino IDE: + - MAC address of scale read from Zepp Life App ("scale_mac_addr"), if you don't know MAC address read section [2.2.1.](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/Miscale_ESP32.md#221-getting-mac-address-of-mi-body-composition-scale-2--disable-weigh-small-object); + - Parameters of your WiFi network ("ssid", "password"); + - Other settings ("led_pin", "Battery18650Stats"); + - Connection parameters MQTT ("mqtt_server", "mqtt_port", "mqtt_userName", "mqtt_userPass"); + - If you want to speed up download process, you can minimize ESP32 sleep time in "esp_sleep_enable_timer_wakeup" parameter (at expense of battery life). +- Debug and comments: + - Project is prepared to work with ESP32 board with charging module (red LED indicates charging). I based my version on Li-ion 18650 battery; + - Program for ESP32 has implemented UART debug mode (baud rate must be set to 115200), you can verify if everything is working properly: + ``` + ================================================ + Export 2 Garmin Connect v2.0 (miscale_esp32.ino) + ================================================ + + * Starting BLE scan: + BLE device found with address: 3f:f1:3e:a6:4d:00, non-target device + BLE device found with address: 42:db:e4:c4:5c:d4, non-target device + BLE device found with address: 24:fc:e5:8f:ce:bf, non-target device + BLE device found with address: 00:00:00:00:00:00 <= target device + * Reading BLE data complete, finished BLE scan + * Connecting to WiFi: connected + IP address: 192.168.4.18 + * Connecting to MQTT: connected + * Publishing MQTT data: 1672412076;58.4;521;3.5;5 + * Waiting for next scan, going to sleep + ``` + - After switching device on, blue LED will light up for a moment to indicate that module has started successfully; + - If data are acquired correctly in next step, blue LED will flash for a moment 2 times; + - If there is an error, e.g. data is incomplete, no connection to WiFi network or MQTT broker, blue LED will light up for 5 seconds; + - Program implements voltage measurement and battery level, which are sent toger with scale data in topic MQTT; + - Device has 2 buttons, first green is reset button (monostable), red is battery power switch (bistable). +- Sample photo of finished module with ESP32 (Wemos LOLIN D32 Pro) and Li-ion 18650 battery (LG 3600mAh, LGDBM361865): + +![alt text](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/Miscale_ESP32.jpg) + +### 2.2.4. Preparing operating system +- Minimum hardware and software requirements are: + - x86: 1vCPU, 1024MB RAM, 8GB disk space, network connection, Debian 12 operating system; + - ARM: 1CPU, 512MB RAM, 8GB disk space, network connection, Raspberry Pi OS (based on Debian 12) | Debian 12 operating system. +- Update your system and then install following packages: +``` +$ sudo apt update && sudo apt full-upgrade -y && sudo apt install -y wget python3 bc mosquitto mosquitto-clients python3-pip procmail +$ sudo pip3 install --upgrade garminconnect --break-system-packages +``` +- You need to set up a password for MQTT (password must be same as in ESP32): `sudo mosquitto_passwd -c /etc/mosquitto/passwd admin`; +- Create a configuration file for Mosquitto: `sudo nano /etc/mosquitto/mosquitto.conf` and enter following parameters: +``` +listener 1883 +allow_anonymous false +password_file /etc/mosquitto/passwd +``` +- Download and extract to your home directory (e.g. "/home/robert/"), make a files executable: +``` +$ wget https://github.com/RobertWojtowicz/export2garmin/archive/refs/heads/master.tar.gz -O - | tar -xz +$ cd export2garmin-master && sudo chmod 755 import_data.sh +``` + +### 2.2.5. Configuring scripts +- First script is `user/import_tokens.py` is used to export Oauth1 and Oauth2 tokens of your account from Garmin Connect; + - Script has support for login with or without MFA; + - Once a year, tokens must be exported again, due to their expiration; + - When you run `user/import_tokens.py`, you need to provide a login and password and possibly a code from MFA: + ``` + $ python3 /home/robert/export2garmin-master/user/import_tokens.py + + =============================================== + Export 2 Garmin Connect v2.0 (import_tokens.py) + =============================================== + + 28.04.2024-11:58:44 * Login e-mail: email@email.com + 28.04.2024-11:58:50 * Enter password: + 28.04.2024-11:58:57 * MFA/2FA one-time code: 000000 + 28.04.2024-11:59:17 * Oauth tokens saved correctly + ``` +- Configuration is stored in `user/export2garmin.cfg` file (make changes e.g. via `sudo nano`): + - Complete data in "miscale_export_user*" parameter: sex, height in cm, birthdate in dd-mm-yyyy and login e-mail to Garmin Connect, max_weight in kg, min_weight in kg; + - To enable Miscale module, set "on" in "switch_miscale" parameter; + - Complete data in "miscale_mqtt_user", "miscale_mqtt_passwd" which are related to MQTT broker, "switch_mqtt" set to "on"; + - Configuration file contains many other options, check descriptions and use for your configuration. +- Second script `import_data.sh` has implemented debug mode, you can verify if everything is working properly, just execute it from console: +``` +$ /home/robert/export2garmin-master/import_data.sh + +============================================= +Export 2 Garmin Connect v2.2 (import_data.sh) +============================================= + +15.07.2024-23:00:11 SYSTEM * BLE adapter not enabled or incorrect configuration in export2garmin.cfg, check if temp.log exists +15.07.2024-23:00:11 SYSTEM * temp.log file exists, go to modules +15.07.2024-23:00:11 MISCALE * Module is on +15.07.2024-23:00:11 MISCALE * miscale_backup.csv file exists, checking for new data +15.07.2024-23:00:11 MISCALE * Importing data from an MQTT broker +15.07.2024-23:00:12 MISCALE * Saving import 1721076654 to miscale_backup.csv file +15.07.2024-23:00:16 MISCALE * Calculating data from import 1721076654, upload to Garmin Connect +15.07.2024-23:00:16 MISCALE * Data upload to Garmin Connect is complete +15.07.2024-23:00:16 MISCALE * Saving calculated data from import 1721076654 to miscale_backup.csv file +15.07.2024-23:00:16 OMRON * Module is off +``` +- If there is an error upload to Garmin Connect, data will be sent again on next execution, upload errors and other operations are saved in temp.log file: +``` +$ cat /dev/shm/temp.log + +=============================================== +Export 2 Garmin Connect v2.0 (miscale_export.py +=============================================== + +MISCALE * Import data: 1721076654;55.2;508 +MISCALE * Calculated data: 15.07.2024;22:50;55.2;18.7;10.8;46.7;2.6;61.2;7;4;19;1217;51.1;64.4;to_gain:6.8;23.4;508;email@email.com;15.07.2024;23:00 +MISCALE * Upload status: OK +``` +- Finally, if everything works correctly add script import_data.sh as a service, make sure about path: +``` +$# find / -name import_data.sh +/home/robert/export2garmin-master/import_data.sh +``` +- To run it at system startup in an infinite loop, create a file `sudo nano /etc/systemd/system/export2garmin.service` enter previously searched path to import_data.sh and include "User" name: +``` +[Unit] +Description=Export2Garmin service +After=network.target + +[Service] +Type=simple +User=robert +ExecStart=/home/robert/export2garmin-master/import_data.sh -l +Restart=on-failure + +[Install] +WantedBy=multi-user.target +``` +- Activate Export2Garmin service and run it: +``` +sudo systemctl enable export2garmin.service && sudo systemctl start export2garmin.service +``` +- You can check if export2garmin service works `sudo systemctl status export2garmin.service` or temporarily stop it with command `sudo systemctl stop export2garmin.service`; +- Back to [README](https://github.com/RobertWojtowicz/export2garmin/blob/master/README.md). + +## If you like my work, you can buy me a coffee +Buy Me A Coffee \ No newline at end of file diff --git a/manuals/Miscale_ESP32_win.md b/manuals/Miscale_ESP32_win.md new file mode 100644 index 0000000..fff96ea --- /dev/null +++ b/manuals/Miscale_ESP32_win.md @@ -0,0 +1,52 @@ +## 2.4. Miscale_ESP32_WIN VERSION + +### 2.4.1. Preparing host operating system +- It is possible to run Linux as a virtual machine in Windows 11 by installing Hyper-V with powershell: +``` +PS> Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All +``` +- Check host IP address (network adapter with default gateway), create a virtual switch in bridge mode: +``` +PS> Get-NetIPConfiguration | Where-Object {$_.IPv4DefaultGateway -ne $null -and $_.NetAdapter.Status -ne "Disconnected"} | Select InterfaceAlias, IPv4Address +InterfaceAlias IPv4Address +-------------- ----------- +Ethernet 1 {192.168.4.18} +PS> New-VMSwitch -Name "bridge" -NetAdapterName "Ethernet 1" +``` +- In powershell, create a virtual machine generation 2 (1vCPU, 1024MB RAM, 8GB disk space, bridge network connection, mount iso with Debian 12): +``` +PS> New-VM -Name "export2garmin" -MemoryStartupBytes 1GB -Path "C:\" -NewVHDPath "C:\export2garmin\export2garmin.vhdx" -NewVHDSizeBytes 8GB -Generation 2 -SwitchName "bridge" +PS> Add-VMDvdDrive -VMName "export2garmin" -Path "C:\Users\robert\Downloads\debian-12-amd64-netinst.iso" +PS> Set-VMMemory "export2garmin" -DynamicMemoryEnabled $false +PS> Set-VMFirmware "export2garmin" -EnableSecureBoot Off -BootOrder $(Get-VMDvdDrive -VMName "export2garmin"), $(Get-VMHardDiskDrive -VMName "export2garmin"), $(Get-VMNetworkAdapter -VMName "export2garmin") +PS> Set-VM -Name "export2garmin" –AutomaticStartAction Start -AutomaticStopAction ShutDown +``` +- Start Hyper-V console from powershell and install Debian 12 (default disk partitioning with minimal components, SSH server is enough): +``` +PS> vmconnect.exe 192.168.4.18 export2garmin +``` +- After installing system in powershell check IP address of guest to be able to log in easily via SSH, you will also need this address to configure connection parameters MQTT in ESP32 (**"mqtt_server"**): +``` +PS> get-vm -Name "export2garmin" | Select -ExpandProperty Networkadapters | Select IPAddresses +IPAddresses +----------- +{192.168.4.118, fe80::215:5dff:fe04:c801} +``` + +### 2.4.2. Preparing guest operating system +- Log in via SSH with IP address (in this example 192.168.4.118) and install following package: +``` +$ su - +$ apt install -y sudo +``` +- Add a user to sudo (in this example robert), reboot system: +``` +$ usermod -aG sudo robert +$ reboot +``` +- Go to next part of instructions: + - [Miscale - Debian 12](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/Miscale_ESP32.md); + - Back to [README](https://github.com/RobertWojtowicz/export2garmin/blob/master/README.md). + +## If you like my work, you can buy me a coffee +Buy Me A Coffee diff --git a/manuals/Omron_BLE.md b/manuals/Omron_BLE.md new file mode 100644 index 0000000..2c069ee --- /dev/null +++ b/manuals/Omron_BLE.md @@ -0,0 +1,133 @@ +## Omron_BLE VERSION + +### 2.3.1. Preparing operating system +- Minimum hardware and software requirements are: + - x86: 1vCPU, 1024MB RAM, 8GB disk space, network connection, Debian 12 operating system; + - ARM: 1CPU, 512MB RAM, 8GB disk space, network connection, Raspberry Pi OS (based on Debian 12) | Debian 12 operating system; + - In some cases of Raspberry Pi when using internal WiFi and bluetooth, you should connect internal WiFi on **_5GHz_**, because on 2,4GHz there may be a problem with connection stability (sharing same antenna). +- Update your system and then install following packages: +``` +$ sudo apt update && sudo apt full-upgrade -y && sudo apt install -y wget python3 bc bluetooth python3-pip libglib2.0-dev procmail +$ sudo pip3 install --upgrade bluepy garminconnect bleak terminaltables --break-system-packages +``` +- Modify file `sudo nano /etc/systemd/system/bluetooth.target.wants/bluetooth.service`: +``` +ExecStart=/usr/libexec/bluetooth/bluetoothd --experimental +``` +- Download and extract to your home directory (e.g. "/home/robert/"), make a files executable: +``` +$ wget https://github.com/RobertWojtowicz/export2garmin/archive/refs/heads/master.tar.gz -O - | tar -xz +$ cd export2garmin-master && sudo chmod 755 import_data.sh omron/omron_pairing.sh && sudo chmod 555 /etc/bluetooth +``` +### 2.3.2. Disabling Auto Sync of Omron device +- After measuring blood pressure, Omron allows you to download measurement data once; +- If you have OMRON connect app, you must disable Auto Sync; +- Because measurement data will be captured by app and integration will not be able to download data; +- In app, go to three dots (More) > Profile > Connected devices and set Auto sync to Off. + +### 2.3.3. Configuring scripts +- First script is `user/import_tokens.py` is used to export Oauth1 and Oauth2 tokens of your account from Garmin Connect; + - Script has support for login with or without MFA; + - Once a year, tokens must be exported again, due to their expiration; + - When you run `user/import_tokens.py`, you need to provide a login and password and possibly a code from MFA: + ``` + $ python3 /home/robert/export2garmin-master/user/import_tokens.py + + =============================================== + Export 2 Garmin Connect v2.0 (import_tokens.py) + =============================================== + + 28.04.2024-11:58:44 * Login e-mail: email@email.com + 28.04.2024-11:58:50 * Enter password: + 28.04.2024-11:58:57 * MFA/2FA one-time code: 000000 + 28.04.2024-11:59:17 * Oauth tokens saved correctly + ``` +- Configuration is stored in `user/export2garmin.cfg` file (make changes e.g. via `sudo nano`): + - Complete device model in "omron_omblepy_model" parameter, check out [Omron device support matrix](https://github.com/userx14/omblepy?tab=readme-ov-file#omron-device-support-matrix); + - To enable Omron module, set "on" in "switch_omron" parameter; + - Complete data in "omron_export_user*" parameter by inserting your Login e-mail (same as `user/import_tokens.py`); + - Put blood pressure monitor in pairing mode by pressing Bluetooth button for 3-5 seconds, You will see a flashing "P" on monitor; + - Run second script`omron/omron_pairing.sh` find device starting with “BLEsmart_”, select ID and wait for pairing; + - Complete data in "omron_omblepy_mac" parameter which is related to MAC address of Omron device (read during pairing, MAC column); + - Configuration file contains many other options, check descriptions and use for your configuration. +- Third script is `import_data.sh` has implemented debug mode, you can verify if everything is working properly, just execute it from console: +``` +$ /home/robert/export2garmin-master/import_data.sh + +============================================= +Export 2 Garmin Connect v2.2 (import_data.sh) +============================================= + +17.07.2024-17:47:36 SYSTEM * BLE adapter enabled in export2garmin.cfg, check if available +17.07.2024-17:47:36 SYSTEM * BLE adapter hci0(00:00:00:00:00:00) working, check if temp.log exists +17.07.2024-17:47:36 SYSTEM * temp.log file exists, go to modules +17.07.2024-17:47:36 MISCALE * Module is off +17.07.2024-17:47:36 OMRON * Module is on +17.07.2024-17:47:36 OMRON * omron_backup.csv file exists, checking for new data +17.07.2024-17:47:36 OMRON * Importing data from a BLE scanner +17.07.2024-17:47:48 OMRON * Prepare data for omron_backup.csv file +17.07.2024-17:47:49 OMRON * Calculating data from import 1721231144, upload to Garmin Connect +17.07.2024-17:47:49 OMRON * Data upload to Garmin Connect is complete +17.07.2024-17:47:49 OMRON * Saving calculated data from import 1721231144 to omron_backup.csv file +``` +- If there is an error upload to Garmin Connect, data will be sent again on next execution, upload errors and other operations are saved in temp.log file: +``` +$ cat /dev/shm/temp.log + +============================================= +Export 2 Garmin Connect v2.0 (omron_export.py +============================================= + +OMRON * Import data: 1721231144;17.07.2024;17:45;82;118;65;0;0;email@email.com +OMRON * Calculated data: Normal;0;0;email@email.com;17.07.2024;17:47 +OMRON * Upload status: OK +``` +- Finally, if everything works correctly add script import_data.sh as a service, make sure about path: +``` +$ find / -name import_data.sh +/home/robert/export2garmin-master/import_data.sh +``` +- To run it at system startup in an infinite loop, create a file `sudo nano /etc/systemd/system/export2garmin.service` enter previously searched path to import_data.sh and include "User" name: +``` +[Unit] +Description=Export2Garmin service +After=network.target + +[Service] +Type=simple +User=robert +ExecStart=/home/robert/export2garmin-master/import_data.sh -l +Restart=on-failure + +[Install] +WantedBy=multi-user.target +``` +- Activate Export2Garmin service and run it: +``` +$ sudo systemctl enable export2garmin.service && sudo systemctl start export2garmin.service +``` +- You can check if export2garmin service works `sudo systemctl status export2garmin.service` or temporarily stop it with command `sudo systemctl stop export2garmin.service`. + +### 2.3.4. How to increase BLE range +- Purchase a cheap USB bluetooth 5.0/5.1 (tested on RTL8761B chipset, manufacturer Zexmte, works with Miscale and Omron module); +- Does not work with USB bluetooth 5.3 based on ATS2851 chipset; +- Bluetooth adapter should have a removable RP-SMA antenna; +- You will have option to change if standard RP-SMA antenna included with bluetooth adapter gives too little range; +- If you are using a virtual machine add USB chipset using passthrough mechanism (for performance and stability), connect USB bluetooth adapter; +- Assign USB chipset to virtual machine from hypevisor (tested on VMware ESXi 7/8); +- RTL8761B chipset requires driver (for Raspberry Pi OS skip this step), install Realtek package and restart virtual machine: +``` +sudo apt install -y firmware-realtek +sudo reboot +``` +- If you are using multiple BLE adapters, select appropriate one by HCI number or MAC address (recommended) and set in `user/export2garmin.cfg` file; +- Use command `sudo hciconfig -a` to locate BLE adapter, and then select type of identification: + - By HCI number, set parameter "ble_adapter_hci"; + - By MAC address, set parameter "ble_adapter_switch" to "on" and specify MAC addres in parameter "ble_adapter_mac". +- Sample photo with test configuration, on left Raspberry Pi 0W, on right server with virtual machine (stronger antenna added): + +![alt text](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/usb.jpg) +- Back to [README](https://github.com/RobertWojtowicz/export2garmin/blob/master/README.md). + +## If you like my work, you can buy me a coffee +Buy Me A Coffee \ No newline at end of file diff --git a/manuals/all_BLE_win.md b/manuals/all_BLE_win.md new file mode 100644 index 0000000..0daf19b --- /dev/null +++ b/manuals/all_BLE_win.md @@ -0,0 +1,95 @@ +## 2.4. all_BLE_WIN VERSION + +### 2.4.1. Preparing host operating system +- It is possible to run Linux as a virtual machine in Windows 11 by installing Hyper-V with powershell: +``` +PS> Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All +``` +- After rebooting Windows, install software to connect usb to virtual machine, type command in powershell: +``` +PS> winget install usbipd +``` +- Describing solution will **only work with USB adapters**, buy cheap USB bluetooth: + - 5.0/5.1 (tested on RTL8761B chipset, manufacturer Zexmte, works with Miscale and Omron module); + - 5.3 (tested on ATS2851 chipset, manufacturer Zexmte, works only with Miscale module). +- Bluetooth adapter should have a removable RP-SMA antenna; +- List devices in powershell that you can share: +``` +PS> usbipd list +Connected: +BUSID VID:PID DEVICE STATE +1-2 0a12:0001 Generic Bluetooth Radio Not shared +``` +- Share USB bluetooth adapter for virtual machine (requires administrator privileges): +``` +PS> usbipd bind --busid 1-2 +``` +- Disable shared device (in this example Generic Bluetooth Adapter) from host system so that there are no bluetooth adapter conflicts, check IP address (network adapter with default gateway): +``` +PS> Get-PnpDevice -FriendlyName "Generic Bluetooth Radio" | Disable-PnpDevice -Confirm:$false +PS> Get-NetIPConfiguration | Where-Object {$_.IPv4DefaultGateway -ne $null -and $_.NetAdapter.Status -ne "Disconnected"} | Select InterfaceAlias, IPv4Address +InterfaceAlias IPv4Address +-------------- ----------- +Ethernet 1 {192.168.4.18} +``` +- In powershell, create a virtual machine generation 2 (1vCPU, 1024MB RAM, 8GB disk space, default NAT network connection, mount iso with Debian 12): +``` +PS> New-VM -Name "export2garmin" -MemoryStartupBytes 1GB -Path "C:\" -NewVHDPath "C:\export2garmin\export2garmin.vhdx" -NewVHDSizeBytes 8GB -Generation 2 -SwitchName "Default Switch" +PS> Add-VMDvdDrive -VMName "export2garmin" -Path "C:\Users\robert\Downloads\debian-12-amd64-netinst.iso" +PS> Set-VMMemory "export2garmin" -DynamicMemoryEnabled $false +PS> Set-VMFirmware "export2garmin" -EnableSecureBoot Off -BootOrder $(Get-VMDvdDrive -VMName "export2garmin"), $(Get-VMHardDiskDrive -VMName "export2garmin"), $(Get-VMNetworkAdapter -VMName "export2garmin") +PS> Set-VM -Name "export2garmin" –AutomaticStartAction Start -AutomaticStopAction ShutDown +``` +- Start Hyper-V console from powershell and install Debian 12 (default disk partitioning with minimal components, SSH server is enough): +``` +PS> vmconnect.exe 192.168.4.18 export2garmin +``` +- After installing system in powershell check IP address of guest to be able to log in easily via SSH: +``` +PS> get-vm -Name "export2garmin" | Select -ExpandProperty Networkadapters | Select IPAddresses +IPAddresses +----------- +{172.17.76.18, fe80::215:5dff:fe04:c801} +``` + +### 2.4.2. Preparing guest operating system +- Log in via SSH with IP address (in this example 172.17.76.18) and install following packages: +``` +$ su - +$ apt install -y usbutils usbip sudo +``` +- Add a user to sudo, add loading vhci_hcd module on boot, reboot system: +``` +$ usermod -aG sudo robert +$ echo "vhci_hcd" | sudo tee -a /etc/modules +$ reboot +``` +- Set USB Bluetooth adapter service to start automatically (via host IP address, include "User" name) at system boot, create a file `sudo nano /etc/systemd/system/usbip-attach.service`: +``` +[Unit] +Description=usbip attach service +After=network.target + +[Service] +Type=simple +User=robert +ExecStart=/usr/sbin/usbip attach --remote=192.168.4.18 --busid=1-2 +ExecStop=/usr/sbin/usbip detach --port=0 +RemainAfterExit=yes +Restart=on-failure + +[Install] +WantedBy=multi-user.target +``` +- Activate usbip-attach service and run it: +``` +$ sudo systemctl enable usbip-attach.service && sudo systemctl start usbip-attach.service +``` +- You can check if export2garmin service works `sudo systemctl status usbip-attach.service` or temporarily stop it with command `sudo systemctl stop usbip-attach.service`; +- Go to next part of instructions, select module: + - [Miscale - Debian 12](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/Miscale_BLE.md); + - [Omron - Debian 12](https://github.com/RobertWojtowicz/export2garmin/blob/master/manuals/Omron_BLE.md); + - Back to [README](https://github.com/RobertWojtowicz/export2garmin/blob/master/README.md). + +## If you like my work, you can buy me a coffee +Buy Me A Coffee diff --git a/manuals/usb.jpg b/manuals/usb.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a088e4c0b17c4f4e8f81c14fb458ffedd1161adf GIT binary patch literal 83587 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 literal 0 HcmV?d00001 diff --git a/manuals/workflow.png b/manuals/workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..7873bdbe983feebb8a01801024b733f77438c647 GIT binary patch literal 91394 zcmafb1z41Q^X`hQA}cBi3lb92EfOLEf^?++IQY&EqBGM%t(%rRybV+x2cP)9I zMSZ{b`~T0mj@P9w8_&+nZ{nVN=D|nqwHOXI5jF?}!jTXcRsex+sDMD%1Fn;OARb;_9{_5X)oq-)l}O0uCV1Wr~Eb zppui$`nlz2{UNs{|3klP1AgV!b<`41hEpkmyD?7_K1EtZ+ce=yd>*TlVW4AsyHDwl zxezN|wz(58_`p7=iM*UuwIV0>PVxqgD&LK<59AKfU$t?*60K0!w9y4jY|ICa+GrZj zf5z*4l~(gB+S&Ha=GOYubEJl0cJYypOPt&5nCf3kKM9QE4iMReN24xoE-~887qo!L zSAS~b>oBhWdHmkP3w-0xW6*U&Jr8qB%iQd2(rC}Q=76ouO%tC1Eu_0!TSbWNQdjx? zwP$W8MeR?)wwu#!ZEa6~y_l2CnxCGI555!o%zAcnqB5sjGF3p+TYq`l_7~rhWLEOe zJCjJn6uw4lD5I*1s;YKL$-JW#0&&@&uE;WmCr;tw;sSi;`AU39UUBh)WR|c7=h7X? zpUZG&1<&udavCJ(>jT@H^`kLX;=gYI@0=eEWNgtlZ9n6sdm7rI3Ou9d99QGTez)Ch z9r*O3!}AC)yY5gmF;&tKT1wTo)Vr?Kvg*KB%{$7)QfSB0f8#wt#}&Wvtl6_K(DP){N1kY} zrbi-f+Qv36m$}AR5w!T$sU77BT<_2DBEi$WEjUT7i_6cp#{nXCJNx-UO_d%t% zRY9_4J^qD5*=@yO;}N?7!FL)q)G9U$ZTR7DOxQ{NU&ktbKsj!Y7Mr4tc!b2`d7uSc z;=>%fv`lK!lj`#L4x|@5MWs-Cpigz?oi?*|>}+8R9hRT{dJf$wu~8^THbcHDBytN7 zDV27>hazR(PA>KlAkoP1uoWP{o13=Kv!Ev6t<7YJaeNIQD%*#t23@2b924_?4#2~0 zzi=MM?K9LKAiyKHeR;H8mnoPaXk;Lm>wGUiOU>pH30NJ&e%I+`Tly<)`ap}DFNwYELmUjD0C{KfG);KjWy_#*PaexOcT@%ZBW zG{#9mL41+F26zQ9`S%+>JBcxecY2(#K8=izHX`F!W0kJ5K>856;K;k5noK2}VXZHDINczKd}>_zx{ zH(?|@%4B@0gbBrl&eNcmuv(1kgrEsE_*V5%Y?#$t6AoY6#E>pR?x%x`#AiQEr>y9{ z%lmnb!*`W_OjQ(j{KUHYWJE6JmDF(Iu3kI{1d{ zrBp5d?wzgro9GX`rv^^u>H|QAG`(f>ea=*?+F7~ph3m0g0GNP#5in}G{hEpm;v44X z=B8wp!banGxovYwru0hq%9dELE`6OFVG>SG9;#?ZG)cgOegK7&mq^7%h(oWEP9KzAg2dm2AMe&JVj!j#R;rEl(j3@nxxjWhd16Z_qRLbzFM^YqI zK68nlCgPO1-_j6&Z!h<4zxOtM!-)~hTedM`*~7am8C*HZ`bPWPCsDAv!Sg@HA@xKX z=XqYOC+W+9N*5|Q-2q1WF+RQ4Gd@X`-NR%UjWGssW5LbM(9O(UVgvf?v@NCnXZu10 z=AI9^1yejnMI{R9UqZt7eFd0y<&*mNc5e!rgxsTWi0}9wq$TK8OhR$&^VaW4KHbqs z(zfLAedyJSP%wc){+>^JiZ8*<6`H+Yt1kx3xjFcLrq^z^QhQ$ zGi8&vOMk+>vVHt3bhOmAU}$P;_W8ZDw3K~CjJpM5u;re$DvTUoda~*z?tD~U`z7}H zhb{kLtjQ1QJPa>$Ip1pX4!1|aX3^AN8AI>+W4mTjX_S$h+Y0L1?R&?YBy?b8l4AQG zK#IOnH8(IgKP$JFx<2Qo>4{IM7x~|M5~6rjbF|de)q#@I9iO^9NrD)A-FuKX@$w?z zv`eOjKsw)F+yh6C`a6#;jd3?!6ivB5-Ahx}G3Dei#e15AT%2}il2a~Q)#%hBO(${B z>E#m@rvi!RUG-kOL#Ug|M({>CBaEp}yARXU*&3P#z^;7@EZ8tNJg0GX0 z&-P=)V4YR-EGnbMiu)(99L|jj;IM%C-fgGS@R67G<+e+2A9w8DU?uR&cT}37Lp}B) zB3?I>Ea}8(|E9A<$UWdclm-WdCU=JHlen2-W(61+g!)VDN_|1xT1%1_42!(;$Kgw_ z9Nw6j?Wv4w9Q7nrZ!F}DRJygw6Zo#=sW zpHQ!YlNB}ATEO&_^mk==ZvoUJLQvl*jy~`EeyDuFPk}?@eU>xALiaOOv4t_Z0s3Mb zYqJ7#?RVVxq`AY!p=TrMa$^kHuYtm07$A{HA$2`?tDdzzXx&)#zYYPeQ7O2=Jmsy> zQIPNUa>u2zi}M|U`~B>RDMW~6l3Lp_fx}`am^Zp@bkN69abjm)jh?GsP>kU@oj5bm znriq{ni_qYkPX2HZ3P{6zpDu7IZ!0wAZb(XWu`nn6u(QId)bewkkOH3c|jc)LFHY0 zrE5{WQwY5hsr52Gr|nuqpvs8len-`Vv!D+JCF)_{xx(dD$`a*wo`gqO}c^qt34YOK<<&Q1vkE`u>D=q0Z~3N$xS1e@ci=5sO6Xoha|+oJ@E$5Y?Xe z)a7SQSiLvWIk*zuwqX;rHA(iQ4eJ$YUC3c61fIZlVJig?=Cnr-I`O~w$jv`v+A#VX z-nLiatptrQKft~X5lQkM=D-E=$S%zhhjBb*vq=`6^$bDNpqjK?IP7b}X27GRBAFdc zx?A%}M52;iC8@sk?M;$Sau>Y9b%)B0;vycGDIxwoO4-fUu(KNCpvF#2 z2UjX#Y-@VornEpkhX9(VLFP~OI5<24ZVUB(_f4m(GRyLIY!YB#BF0YVGYWML!O*r7 z-iT<3?uVNEIbnl3plz@{*40%hSrMt>3H@~!H;+8b>LYeTF$Rb{26OZ?Z-Nbv6m)!C*%7JolWC%@%E(~vn+*HtJg6*D;M-J2 zNn;IeNHK0n<41NJbjH`?9ywHXIg#Z5LL1VU_v;@t`B(dn>%X`(2*wlM@j|O>c;0@8 z*GKq{r)04|wv>9RtPWSPP}g-}QQxje9T~4m28V^x&^I-fUCO3D&l*tHAgPoUJG)7f z50o&_AM{(h{&0-zdm95{Z@a+H9yjLpZ8zb`=AOTPhyzJ1JfeG&`>fxR{qyC zZ6?DnuJGkwH!Gx>YA-J>v^Cz3&GwY15f~DF@=!lD*vRNBSY;$4hdD1z-RE1<6}J4x zXi*Bmt#h%LHwpQwv>r6-g+#u-&m-ld_t1Z36odGtc-yOXbBfz+fMA0EsA5VvL|;E5 z@PIWTAS=n|{Rz8N)ekz|5T?v0+M4ha>+@|{!M2?gAJttlQgk^4{fP+p;xonc{#?ll zVfksjm)enY`)_>dU}~~Dx|Da@acQN)2n<;HMU0`C|78W2?^||eZ*>Q{Qe>7u=~5hT zXv@i`px9N(QS4yVrZ>1pP4CVsg!~wCpS0QuscA4I!+S9_jqct?XX<~H^dj9NaQEaT z;eDwaV0=c&B(+v46d10goeFP+-r9#NPVL!8FfedsKB-TLd zkAu!OfBP>zUXPFMtS7fjM1CO^`AD3kprc?PSx@KT7p5SVuOa;bOw-SuTFa=_HW=>! zji4dIw{0l{%MZQ-xC}PX->{udlNVq*tdLwUX%I39E4sOOx`aFbs4p`5U32wEuDhkkS=^|0^FT{gASVAE`Jl8nmX?-|jzeys_m2Qj{6|#K_5W-8 z2?E{z9td_gKRF1dmE}Jgw?%86GG1BazCN6PAayjp^ybO>fgCD|7(W=! zude390fW@o`%feF+}vzwcRllJE2ucv+k}ZXH?&5zN<((5wXkaVaga&?_vT z&hf^6kLd1|L=g`kLpt~t$aoztFWj!K)byzBBx1Jjb0=xIaAdwjQDJ17Ei{?&IRm5v zr=s~y`wJISyqP<+(T~CuT?uDG6*Yh>eln(kV2hzCJP^oP6v>>+nH-V2zmifS6aycd=1RBMnCqKl!tZ5P6rf`r^Asy;!qvo$deRHcNfir;JAzJ%j3&QF)Y$hi<%Rh%15kcrI))a z0!|7n197LQIQyZEh|thbe}Dh%>};?T?win0?6^c!(o22a!*HVNPbPTs_o_1679#Zu zR(({JNQuwYtO_(zSk#q8K26X|_LRv>DdO9|bUt=X&9Bvx&1A2n3WchP{w|(*+W?2) zaI`rA5Pu_MC!qnZ`{DXL=L@;+E7@eohglW4LVme`oTn&1#MxNfN%mdWX~S`4Z7ZO( zn-1lWl8}_GAttH#LP7-YQ%NsM8IYViSvH&)c(`xdZfX&M31-$+8#bK`JwyU{wVR3`^7BT<%#7*RAs-P+Z)4Qv2Gj1L*sUA z0t$~Lcg1W3>y>Ky3sb{5^U2z)vlx^O_rI-1&Hkt^O$u!&?x!IR%xU4&dN$6><({t6 zpLU0Eh`V6z1x-_{GOKh=ay#ugr%@N%{+W%Hp zGfcblbWwqLpl>^E42s+Ys7_5&Y@L-qNq!tSlob{@os!yYQc2m?d{+%D6s5I%{KfPjtU%C*3!8Z6}Ylgbf-- zek{R)pWdI6Ns3<;Ew$!cuO&n*@q?7|N=g=4qRWGv@3h39Z6d3_Kev}u$r{ShwvD?M zZ%I-OcCfPA?s)pE42ringpLWg@|SxW4Alp7+ydt}WutIuWQhwe_X6ozY03lX#3R}W z_)IGTsHOE-N^9$%W1r1OwvwzYjRvtxl6hvjaM6sUdirvOTN!zy%4Z$2R5djpzffeJ z@U%|%`Fj1mm~{dP?*Sv3+y2yrskdC@zNB?{r}2kcZ@I)PYMm95@AxWvu!#ubvvti=NOv+;|InrPmNA02)@%>r8}R`G}~f)}P- zPW#e;;fiIpgAHkHljBUK+Qex$-}`c@#&_-%7Bd=pFG|~4ko2U=y{AqpQ~5e^Ic0hF z>v9kA3M*717te8|Bd7Q8w99QoV5bKyZI9zn zvX?XGHwCbv)mRksYtd7aCE*Kk&+SbtEq`^=#K^V?H7CXN^ekzO-w zKjH^?(UH;;DtLz$($B4LhV-j`Drs&XEclM=BX291?R#BK)R*xFuEeEcM8fH$ItP7f-;s`*5zgdf#>+RFUmOTU>4MxZSsgL1G01 z6#sRWp&sK^ny7P1S-SyOnTwheq9LbtYsoubk*a`4z zWER32Sk1j$96%>y`!?srNW<~@-fN4QBnqyNp6o~yL^98P_tYQlSJgX~_5ak`gru|b z@uF?Yr+q59B;s6*XKwfD&1Q$*;~;#IYQ+LF&uDgwt+edmG=GA_rR_dFUCB|t);9rO z1urVs^6DvDsp`mXfQO0dK!Yslx$Q z>SV6@uG_H}H|$(AFG~a~LAv^KS`5g{LWbF!?s)Hv+?>8h2f)WvcAxw@4fHs*exHgR zOXP=q>7*tzlkVLJ(J_9Y8R+Zy>ty7-Q|FT&&4c^+p)VS`#xeZzxt7yp?C2g92RVEa zSAXF3fIwxE{LS&*BhW@SBPMY8CRc|aIc@Q_=e&Z$)o`hIIRyq53G(69UYJ1AY>{8N zqisQL?7PK>*|Vhr2buz?mAhVGz^I%91L3TQ>FWZI3w>XF=-A-<2m;+p`sMT=wY^;- z>#>&vq^sd42|D+3&x_M}x7act?n0Bcn$EGY1V+yJp5e1C}lCz`$YYQK_h_7Hu-3Wg4Q zmvX-5e4|UvcR5Vy0Ml7vS)e!g-Wb#?g31?w5Bcekq^3*i>{Iw<04 zqapyag*bZ|8@0I#8(d)3r5IRZS$=qCq zbrFq8WA2}x2EZ;>!#(vGgs?ir0WkwFC>0iq9-dnR$n$g04}L)9j1+VNi~r4K_b|CG zv;jB@08%(OQ9*tLtgHRovx9qe82A+41{rZ@`=3r6T;Fl5wDsKty*JsfyZhJOh?gnQ zwuzrpA71%SFXF`NOu zN6Ek&c9sHjx~vQE#HR;zS48aoU|oIc`y#9CApnsLyt+!o-g@x4ULG7xo`lh13200J z*LzOmH^^pJ)LU4u&DDKHFuyb`+yowf+dX2&HckY zuYo#3-by%eK!>GRzYmRWxgMDc_TyqhZd0cie)z|-76xyk?_8kWStS9B!j8%rkj;H3 z++A=nrSFGqWjb1__xzZTvcV;C$mb0h(E*RDPDvlmeA+@j@@CE2&$0@~*}sQwR=ew% zTmiSny1 z*kEAJKp4&Q{*md9!Ay2Gb0n7g<`UxY3dsuK3N0HQL|*Qb>3M2sY64+sWeWv?>?HH4 z=IxI^)dOPLeao+fTU>dpvEfX0bu~a6MPliUJM*i(OI+wDV1b>D@BM13K&^65o_|8( z+<(kricWn3M|%Q{ws;6qn^Y1KlJ9&jhw>n!`ndoB{x9M9P$?m%yjO3o#8iW`ac@pm zXToOF6@O^m1MT}88@_qx9IQV5nCyH3KH?LmSUPo*GfCKX+~l8oL;YSL2=s(4w+e6u5sQ0`IkBEBv$&Kd^x0U#&9{lSS!7l^?ms2ME;=bZjD8&5@01x z{|u*J;UVPtt&TGSPDi{dWy$HsDap{cKGeG3UQ)MaNes|aOExFhW9<1!mLQxLloE{F zM(Kaf-mP(mxxXbYUB%^UEzMd{)<3v`uP(Mu6-XC&XBUWo-hjjc%Ldgx9r4fJex6pa z;#*3iaL19C0`CBCejUeOU0Jz?=CC46pfzr%C9fB9t9Lfwoq%=*9&Vc`L!|XwX6i5g z^G`%9)1C>awIRgb(ERuqFux8auF*HupO9QEt66is)~3dC|)XWg6XxhH{7v}rpR$}He7iJ@Ke zsi=Wl@lnm?$w2$BiZY2H&2McQDM4y0UuoFdo)5o9DGxtumMLb5W=uZsW!ajnfU3&( ze9&_iB)WI1;x01Aij1en_hbHvO|RIe(;|i=8!JFbFZ_TCX#tDs(B=Yxtnj2cA??lz zi#t

(Y6n!cvTdwQu9r4!9jSB(V~KL)3AZ8*|>AsBs(Xgmu%Kc<`NAUfOS$1>9rk z8=>uLQ7$Y3(k_t=Q?KsK>VhE|5FPf^U3>=-!jWQ+J$CMp+P-o2S^pz;u(Ah}h_ zAt*_&^;Lu*)?M<|SwoogTg$2fYOrLhK}<`2iW0SEO2JntWRbZ-JpOA1om>_JO@t zV0KPOBDftW@l-O3{Dq1<7r^J$%J;fM4IqTR%5OY;_>Zbimm0R*E>-S*=*~I-+CyfZ zqhTzr$9c8)OfvLKYC|BCuNe!gG5NL;Dh4VdRXEMq9EB3Tz4v?y^b$Wc{vtfTH&*rF zm_{MQnG$LP1oUdB`sX(3@3ql>nknpCqR#W5{C3SlOp4R$KMs*S+0cNW9K+&5=8n2b zBpdkyIVFhXxa3-W_ocTPv1>dj*Sysulc3dk6xdl$0{6v}X0HXL&`;v70Koa^ zt%Ua>Ox+AK8sMM-1QDbO_gI@3yR~CLhm+f)FZz)LlR{r1oVLVUDSrk(bDhA-I#bC+ zsF5bHC2GS3!Q7vN{WQ8ivEs>vwUIh9XaUJF$w+W4{JcwO8j)`26+4YsxoDN!5i#-{ zgsQo-nk~PLYnN>2dBc%XX$AsK(-C&on-~CnKhS6!88Mq(dnEy&01pxU8Ta_s!>{0( zD5klh(utfZs{pb0Ql>qA$*BZ)<%;!(i3bQb%3J+v$V1;=rLbDd zP-UfaYLlodIfYH;8$YIhq@5^I6RB&~@j}&Su3f7Ih_w|vruoZqj*|BY$r;Rfg|hq! z`CAu1r%q-RHQ=hSnyg#=r~71WVwnqQk;x$%HY1{J7aV#^a1V-pB`FIJ)Z{mbq@WzJ z8ZeDQlGw02>cf23<3RIiU&rqYnH?5VC2eM@$$ON&{J@OSRYviHzg6VWdf@jq1k}5#YilvLr-#gQUIj^k4@p8tJQ*!r) zyiw}`z~o50{sOFdRj%BA4iJ?Isr49;L}4OoV^6CdDH$fVo}Wjssn|1JPlBq_EkAY{ zXfP+a#BT-aB4yoQ&}9UsX4RJ-p5?PspDsx0Mr*)#!lQ$wl8rHPGgN>S;X9W87>Z2H zk!IoT0NO^-wy~~9>kCSsIOK72-ROM^AQyJ=ql8B^SAP`M1|eX(g|e3k!aJJcc*{VO zcv5@QU9=15(Z>q#e+wtS&Yxzo@nu4DsADAXI^-9Jy3)zlcW+J?2wW}2cTJqoBKn({ z{V>kRtCH*;OlOAay6w^Zkf_klZ7fYPUNrCk3#=Bw1Q##C*JZX?h$9F95I5a7j1kNc zv+|mNv%lxeUsfbc4~6j}KM5N8L*& z@xa?pLle$0hHGH)p&F7kh|D`mYQ_nc43=+IQRhK@l~63sslN=3J1=8pNp>zW=Q!h@ z8hh59WY~)-aadpULy%7BraZqh3}8e%sg5Pqmls}c2*2m_W4GKVqv>g zL?%SD!NA1EA?vm`HfHlSD=TaM5=SfTUzQ5pmrO0H6^`n_E@ggkkr@+|kygJR6yMp( z)EX{fpE;fpU6WzkF$*TNelOjc26Kk1tCe-)?Kx|X8R)vPVag==IMu>u z&jh0(Na#jkaA1#DvZ4x2=j=ZR+kr7QH8m7+)$LJJ3nj8cr1cOfPcC?XHY2L=5n}de ze*78YqAGe6Zh}sb3`}|ClF9NxG*|=+ns6YBT{hF3U3F&x0IumUh4W->2>=upE7TrD zng&_opK11B0Sn>unC2{NsjmH2NYT)K%JSp|4_KcPbB0`>SpE6SwHV7YbtK+ev69JP zwE8ESLjPn+#9XMiCJUpaK0f$5VT&tW`=;Xv_73uyfL4ChKs8&WA?>h45PjzYT8#+} zG~I-B+L;9iKJ=DYQY4sWsEX>eMGV2CCQF%|kyp#KYTE-dZoqhDI|wwh?d8@0R^~4L zFVlAfZHHQVjC3*0)c46orc*RnI_^HWc}(+p%b_(t)ObcPnz7=YVO5 z$tN*%h+s!C7;}s}zRQB?&(YwkzlGt?F*Qs{HG)op)MOTQ^Hs#*O2wNW+T7{s8<=y$ z7Hz_DI{fZ1=l#aXsQcVpTvw3R06L7?OX5BMaXH-z=kxiNQIGLp#kA!EIxC%Tn>=lw zd<^y0^+*^*_o`_>n$bMk1RU7u0Wbqlw1Naw`>(>RKi!9Wf(kP3jsi=h&$N~QcqCPG zEI~!x;Nx|!NI7+UkIJt^aqq?lRmZ*R(wYSqlaKpPa!O;^Guh&#>Y2Osv`Z${k;!L{ z?%VbVfe^WTTgmw?6gySJl)gd6qnEH=(#y5I3C1xu=)Ogi&gWza7UPX8x;?Kc)h zK0zZwl3=sIL_r&k(hZ;yS{p&@?XYu&yjWpzWpj@S*8^gr(@@omj=P9+e^Q))sV7U9 zg0190ZzPk1=4}@Cr|sduF4spnMdlx1F4^j%!BQoj(+nzqnw&$etPb+3n5?4j&=SQ` z<>SS`k7}r}R1Nm3u#~~cws2^~x~^9Hezn|?Gyp}0B-iW#Q$wy&E8IgOi)Ky)o+dp2 zjS$xr*lC@@syELZ>QqH0P~;->lRVZjUKMr&wwK`Hb0Sq!g?lsx*@rU~wQ(p|&l7UC0 z81!;+XEAds4+mC1LL1jj`o_e*k+``712V~!s*uav_2JuUF?S8KVaw=fRP=Nhv0HNV z;=>&yW;T}v8P(jL6=5}ajUI>R5Gb_zdP|YZn#MN~l~|dZwoiWH068lo?E8j4o?hIC zyxK=!VVH|ZJ0@7vkTw?^D?l06{_p@h02_)DKP$I;r0q|$w=z7fyekGy@46@NJ+cHi z_`ugjN6BU6&|#tT)q4oE4eGGJdQykQ;~W8e+ckxL}6f<>hgaO;dj8DQ>5qvQ9lQ?;hB7_XZz0OXeG zIeMDXVLUcAX7N???OTX+E5Zb5kn{6um4H@={1q;yZwJ_q+fyQB9vees5lh^YJR<;5 z*J$L5E{5v>{quX-95g=_Z+ul`+3!<$pU|Brefz0~cr6hLPMv?j`_`ke9wRgCT)Ft{ z91q>AcG@fklif``p#ylEFc~!W^~W#gHQ@JCj6FH2gs={jWCivfM)q~Z@~x$oQ>Tt% zC}zZw6RX4TP9e?*VOmdg#RW2E$#%z4n?oEKvyF@jo(+<1f@uQ;?9I>n<}}J0+&Pes3=4 zrc*brOe$&X8Rfjzz+2ECR^$AcaCIWMFa!M(~5j zpBWTKN~awgdI@`T*$ov#*2~DVGg|N=0IJdt)}jBWqL)L50XL&M20V^1ivn=IA1A>A zZt|(cXs*WC!l$*gHssj2AO>7=z6%j~m!vy|D0fDU+}v^()Bb2H%SR)>_(1|*H9%)< ztLAaS!VpNLcHhF-Gen_`#E<30dC13OALhsgG!(`T3F^f5nbHn53vGMOa^71a;h~~L z%c9oDkmWe=lX`~8XrbMxMU40f#a_&K7_~7jjed^$MP+SKoe;2_Vb>tgceYO>#VYPh zN++ON1NjL4*j;cnBl(j=v>?)~$LL(^_1ihne4w62@Xg2Eoy<>UnAsW;sb+6H=n1M~ zR192hIp?jNANOQ^a_W;avlW*oqLdGl-p@OJm_$aYY$R#yL&hSKPWzSu2sK7uoI8aA z7?vymKAH5O=G*-#vWguV$%-=35V^2IO~v5NI(DAXKLp}mGNaD`3#C~rylZciGhEMj zbp*mh>xk^t^ILUBpUvPY=lU>5^COx6=`IgD>xqepZK#1+9l#kG8Fc`+6;O9JyB{e1 zaUfu95@x&stZrFEX}%p*hx_uYmlW2rNLRde54kr+LZlPeZgz(UOQ0zGL5Z@b+xP>VIV&`kRu)ciE+!`2iMu`}BUj4E&>6WfV_DD8$P zLx+L!CzD`67~nhYdn33uyYsEHJ~@1}ufKo0^)B29A^H-I2QYXpH1{`&n|D z-lLQJQbiTMOw~zHsM==oLeUIS8Fl^&sm7eqLc${7=tx^2qaZk-Qu7O7n=-Py!cUxq zE}(4kyPDfSgGqZTFmgM)!kjE)+V1PQyZzg;@4C=SRgWE^O&G7RaHRcoJtnH&DL%^h z`Yk)Vp?!KU@X1-s6pEC`$ixGj<1LvKv?%_sPBWGi=rm2{GcXzU0)n!Gd^y-wsy-J5 z>|7+d7H3i#mI)6`#gtoGpX#Sk=SpVett9ble*ptlYx~6rB zk*AZPy28IDLs#$Cm=7olDj_5I?5A%Z`Fc4;a2kO5l!Jh`bj97PhD^RU;vO}`b-G$z zx(tZ9;rkgD5(+`PO^lko_fKWM;}-F#mw{CtXo{muR7JY1d`!fob!|tHeD3AZ{lvmx z_AUB8ja}uz>D8}Pzlug{{MNIu>VyARZr)_dB6yMeQw$476llqr`l4PCmIHxr{Kg{^ zl|s!F52kSj_{7CRx~q=RMj8h;_?}8II{1So9Bjys+KEpSHPIH?u<{9t*-gpkfkw34 zPVx=+l(^2}U&Lc1{k4rqYa$S!2}bCn@BoUxou*o0{BP;AF`OAIwQL`_0ElxowO7E% zLiT_bagr!)3RFagzVZ8gEf|mvR{bZ;Y<-*Ari&#l9v_w>h-lmdLh^uoR73Fi9p?)p z+k$OT&XT@;9-S9N$G;=yC%X^_AoSeoUGFu3rE9K`S4Gg8WMNWG$^}p)G%4SP>V+mWW#v zO8+)Slsa7KPqeQX@;|mEf=0-hdMHQ0Bw3l*0%;2C$@DI7A$=LBgp)kEXbXoSbI~<} zd@xd#goWetg4fjNoG!+0jgUDv^^<8*1`x>XP!~)hFCausHed}iQwq@%v`jLxs z->xoKz4qQ&TBuWS*Ss{9`gy_~kCF z;%*+>cK;KE-Y~E~SZf6Lh;70xG|^zFqC8B{i541>Hfx6KIgl#0bn0!2x;qoo&C7H$MpDmYmlZ4W*@hOE2sDasb0+Tvw^ITbZ(mkmno#a zyuS0fe@y(#KA`82GW+H(8l+!tx}UGBV1I9{Jo)*`1T8;`IH%1MU={=S@Z+IBMldA1 zHqf(#oV&U;R;a}9#DZ;+ZOU5rgzc$OHS=aeWKW4~cBSB72@ZixE4e}rcIS`2>Tu=` z)|41<*jaUis6-UGmL0%7-^X_%N!0TIKra0BO9+5;R1Sk^=qX;4tjFD-AVgM51~QGl z?j*+3ndn4M*0$0g_BNMII+!SlELy4mNZv*_ni5H5_)||fB#!3l!L6A|1dOjq9&7Iv z4qq^>#E|J6GAki?^$nxiruOuK(aBBv%n#rjg>%3Ei7yT}`o&a%K_0HydMW4XlRctvIh1E;=>t0~AXmWPK>IpLesp-qQFQy9Jpu+z>>G2h=c5#WW2U56NrfQAY)H!B1GHzK_|F6-9!r6_E9>llQK|T$Vx=^A zgqHxS224D?)bE*HQyj*+(S94v0DNI$b98y(unts5B8`tVxR*P$?yPFkC%e@@#w^{2 zpeZ)@l9kjk)Jsp>kKrf=h|?`x{Y^bIDSpoG9>OYNfFP5jCQA_$WDm}&J>MVgO17k= zHiZf2Yg3<09$6Zjy*4xt4i93=llDKM^S?F9L=pDHC z=IO~CNCh6vQj43OlWzOHu7z z*5Iix@oA+`nevW*eE1kbLX6p4eC((e=XP>8MRC*1Q2JPx5?`Hy=6obdVPi#H3b-SW zo=03RA=S;!y3whPkmfX#KA5_Bs>Y9yVEJg2u23Y3OQ)--tj_nHEG2;w+~dOd;e+qm zF##Fg$IMK@Q{t`nRYO8TNYv6k3hcJ_jpaQU3-@EhN=!BnYHXCD^lx1;i}bW)I5u&0 zLuJB(FwxtRip0`5(BUt5XRVDIQ5W*{sTF$@xz+pisOj7%i4MTgcS%@W9laFfiu@_8 zUAKn|99JT-!$N2f)-C|`aYCbNc699S!Sv-E-z?A7J^}3R6nSQVDS|3)khvckiP1FD z%55&itu0KwhzF>w#>4RNCV_m7eb>CEnNTroG1Hh#ge7sCeq7#rKcA9oTVKma9{%Cy zeHTg@V%7tdkgdB>5Qe;$m)<@*zj7Y>n`{`9V|QV;Dl9&V%J|gyS~U>Du*8^q7D@fJ z5K~?vx32}z)74h}8-!ULezH&Yg8r5ht{Sl10Zfk6;N8slzmL-9?@ilH5cDrSoc9v%DKapi0{PsoonVW_gsKp0Qf@Bl$evumCiJhLo`t1mCw% z_oS}gUjV*ey{hYo4=kX9U1}A$rGH#Zg@3lo_?cPJkKb1-tr-Z&pX7cNx{UI5hv%!beTT?;xZhn$3KiK0^M1Bgh^uMZLRX@uVO*<$Q z7K94=X0#A;OWzP~l57uDlS;pp%;7VzCf+Rk6WsdN=bXuxY%?Ipwb1$6jp!avinee8 z4C`Rm6+bj%x3b6?K?Jm8Q2_N=k@6N2Q7_7-_WFv<$_LZ@G6~uRkP(BLLe$xZ{3ysp z;!vrJYpxidgJdxVM z{kZa~NvkmO8hkoxHK)jAg>dJfWLy>NN#FUi{TMM6VL16NGwqZF9&i?reK83Q{TT*E z<~`{<^S>Smv3VSyES8p1*R8HHFnZjn`#pP$e`Qj7l|re3#VmkD(}V|!J_tXD_LdUc zDWCCeIU;I!W?lG**kvKo-$b0IpP+KIrlr%hv^*9M#z+izs_#y5-y(T{BK)*Oc+H(# z7e5gtNkFD#|n@%WqZH#I(GeMaztn+zVW*} zd0&xZx`2G|gzpQScNltpNMR^vI6Nv`o*jf0jNTzYs6Wd3rfuXwrbB~#s8H#uR8D?( zDq#CTgOr{8_u8pkJj3*D8NJX=$`{vLj<9UFE{-?Sbq=F7_T?8mv6sTBGIrS09xjH41tGV{a6TnzBN?;m*RwG3DMpf_BeJt)U~EP;f+i_@;vv6hC~V8V9n5fS;yU=q@;C-S z8rq71x?cff%Zh#!u>Jf2Zl1myK3MYg0C7ayEciK6t^m(bu%}@B1N3-CKSOQiCa->% zvp~zPEov}=o~tUOum!8EtfkRu;&ddcxQ_9OtcZHFUbB(a_7f(p$p+R0z62OMzR1T^cZQ zi#PVrTh`X}1V2Ua(zs{%rHO3ucgG%6IdXwbdc;D}Nl;lG8u-ttDen@SO*jQw3O%$r zL8&A`n1R<1RC)a5vvZy(3g+&K^@dxjh2!GQ3PG5-bE)He`5{D+x8s78Rh z6DI6#kY3Fm-gpSoa*Sf6=HcEB^96=A0Dn{c`M6o>{Y7sR#)Gan0oE17sUi}eiB&{j zYOOGM>(D_gnAe)&3__qW{VBW^jX~NP@zr?GwTf@VK{aDiMjN-A1*e{E@(Uwg7ZGvkA7Vvs@+G zTYx#Y24D>Z;vrD_K=Y5q?Brd2d?DW=yRIR=o3qvE#xp|Ya7kO0N)1E zzW!IQ)+>#epGrJvb9}IWWZ@2->QY2OmDk@M=PMMzlxiT;?0EC*q^5sQr;&hGca0MJ zjE+EQ`ctNsa2*FsPQl#^3~`KuFv%@AQe7|@KFJv1Tx5L5eC*MBBl;}FqjE`X#4#Dj zDbVErgkTx6t@kZ488z{d zd??znklKQDD+MidR!E&Mn8JHTet0UV9#$FOPo}red`s{;V}W@1IgVH_m4M0u?_*Im zrUC3P?|-H0__C6o)i3z1+7&G)wSn=EXA*r`Sqf|8N8BHO&~r*^YL?uD+W;!0w6VT; zG_U?8BaZRi?r~6Ge8+Vg5j=5T&hE>$*&FTYru64h^v5aYBaJM76fzIU!2f=@!}&<6 z^!+Wztx5YrLc5y)+am+CG<|)4%Fv^$G6XF71HUJv6W1@h%V?6b z>g)LUPR}oeA@`0{iM!F+0q|EphMH&eeIj_r@+?MQZpToCSxwR5rk;5U+mL8dxxa>2^`YVR6&cV*KzP{Y$B%CtjFvsz3{-)kPPj_ z))*CZcd%di6kV%sGx-0qHTJ?hshkhOu7Mb56!Fjtgj-xgudgW*+F}2(fnu-o?<9i0 z!L;^AHJD~uG!NJh`-1G+C+MilP*X`}dHaRAWe&4$ci<`;?ncXdFvWJV%2D&BgIX6# zUlisf`{71lNOmV{fDMEn!pdCb`%0ISS^)j7&e4hy?T=zsP?ik-*nVK*< z3}xI|ex8h>(oSB5zj)_bZWoFP^S2up35jn#bC)<>ND6rnw^uhbF9bQ;hKD4-q?gqH zNnG?ajH)}NIKt3Sv({p&W@C|c8ShWLrT!oG-a4wvu4^Afln?|IWFsYw#3m#~FaQBb zX*S*64N{UycZ1T>u?Z<*gCec8lp-xs0xBVK)(!Hg&-;GgIOmKr#`)*>pN_%4?{%*= z*PL_Bd0p4a`F^tB=#P(A5O?`p<&;eDezko$3;xQ}cjC>ATWU2eT%*Td5|a4&dd(Jt ze7Qa2UDy|4_NBy%bQ(EUOQV?IAuoG^!njKN}ku{x>^vEWu@@va6pRHFv21V&H1jV>|UxHEL<67B8mhh2GTC z8;_=GpWg%-F`4^jX)G3{dv6X>9YVFIG($)XX%N;E1j=zsA-FI+fuj$X;8y@E zZ#vP8lkOXZ;~zsJnmZ%6c^kRe0gh_i2+u5vUCsQrHG9NH59aqk8HZ*l{x)C&b)9zs z47`!#@~RGoPZ4N(=Ae%(f4sHM;>z@G+aLaT?#+7q>t<}r1J;h)UEk}dpNQlWB{%mh z%jqRYOQR^ypauZKLoV5Zl@|F5(%KTCQOfv z#Y!<;&!I_!0P^Vg{FB{2v%0zpxTApAXD)B~V|&~9W|X9Fq908_ggn4Cz5$@`4y`Is zu*3Iap*EoIxNp*zp`DeV3;F98+_CL55DgxyZI3{Io~CW&1@hy!TDYBfvN1J+xom1qK}rQi@zJ~E>4}|KaY9=4ES4m-EG*^OOI{vs(QQmVLj|j7({MR z(e>u|YDW;|uLWPjMoYj1y9I#O&^#9o5J0=^XlFKZdGk9|u~)5Z*@MvpbU95oACP$M zz6S@s2i$1M9SpO7?s3y^2#6Q3I$rJGBx4G?20oXWJs0jAJcd{sEg~c|0iF;-u6akg zIV1KBz}5rW?xYnunsbNBzn{}sQ9<*r-O|z$Vu#KxH+!CHPQq6x5utSakv} z5<>JtpY00nn`*_Sr8;#Epvr<4^;k>ruWu2LY^$%iyl|v8T&mmLR8x~z?K79%ybl1E z%!mWpfCN?BgPq<7M=h~?rc{BT(7(0d@d(y2|JQ&PpRjR4rSfFcrxjCThcg@ptM?jl-q7KYfqDLtL4$i3txt4g-RF)eKa7U$H>wo*x*Y!80F_T*N%nw$Z2Hvw&D3Q^ zPcVhWE~5m8Yh`}(JR)Wr&RfH_LVy+rol=h7PeUQ@+?jcxpSQK&P=zM#r=|P%(8kfZ zK~=>4aDSKRnp|zr!~ImVr2O9=v))@Ub|9$H8B!k?D-oC=my@#4jcmoL43tc*dhTJf`Y%Qz=2LAOPF z6cB*s>cI*4#3uqgAM)}oSAgDi!R^z12mq;*fDQq24+sCm>`*JY=2mSejx=p)fNcHX81iHU4tt)e#S{>jz zaZ)SNkLL7ba_V?&r!nmRkC#}FO-(gB%!kyo^x=A!pu8U4rcO-KGFyv* z4pnE-sNkH48z4%f-MqK9zJ{H_w)i%E&{Pa9iE{DXGuK24=%j+S^RIzhNB=~o-M{9& zw3B`GLpNLJ^DNa*HDPQGBg&(%%~8@Tz<~e9=Uez{H*8KG?cX`#hoaG1vrQL65C$J8 zlAxi0^Mag?+UA!pUs_sxgU~4;IvJSY7jCZS`d}c%HUU`Fv17PxcDsRB+tP;yS6=KB zeVES|-4S4Qtw9OQ3?R2tAt8`YuMDrBE)u~q^mzbi&%KIKsY>?C|Bp_#6@0)zhX2tWF<^M4FZ2=?li zHEou!MWBrXIA9-8>45kKXsE~S2B%Ygb>zdPyPBXrlmdDKCdX6#6>;Iz-d@!UqML!D zvj{gaY;@&#wXTNhPecFskBqC?!WZUw1B^CZE0x%DcuJw zw9hoZhYBcHw@#-g`?$e||IZY348cZUN-rylhX4%>Ewbs4Z*zN{rR$oS+|AoVi3#WF z70++4t=^X;63ZP37#1_nfS47(KkB#rAd4L&*hn?imM^6WvIpDRhSN??SG+nWf{@$L z4^Wdc2T!2PONN_e-TVxMg8o`>C{3%bu7=XIi>Q7mck81$ew@N9bAxs@)M(83Hy5XH z7l&MUx!&Ta*|6H26U~v5$Ve{5FTn~t#?fLRO!xik!F$_-ws-)T_xmdF8M{c%qJMl) z;&f?AZ^3}#EAhMVb`TqRE`P#?fHI#i)-^X|Ck$!oSs1D0UWsOJPu5fUs3IO?pHj~U zXCA!si4xSV!GotfmNm1e_syJ_IZ7{I`%tmVzw%#Ruq-QG>vPof+rohh8HyRx(6c$K zreczqGl1UBWD*?h{5>udm0gX}c{siQdE%DK%3IfF*gZd8mbCvn}6Ief5Fg!>`X{P zp(~NG;YN<2Ys&oZ&s+MSDY-`MgXPe(m&3sOqhpetOPYjUP{!?kmx5rYgVWq{& z?@2irc}DE`6)1!M{c(73&i2epd=-nVE;WTEC6}HSB-pgn4i!@B+4nN1d@DBvjzemd zup;_+p@9VvJBohG{sPEiA7TpD0OONPW#cIav#hx7Tm=2~ngsk9+2$@ZF1`VFkTdKW%qL^w) zab0h_kjR3KGuI$(ILhn*y{vJi_L;t9|0~)eXLXw`q?jqhoc=%f& zaNYtviod=>dYR?nJ1-AhD=TLVcf&-y0V54PDTlhzx9Hqh_aeMlPnmzbTqn#w5=P%c zcJk)CE!&rh?-qy=J{g*dwv#5xiEEulI6+_f`his^{SSZ*Q_P67vI(`F$&V>fRBzAK z9p_{23rYEiQCV0*=kRG;`U(Fe?`lQ|Qkg*8fyoNM<}pIx6;x(OvV*(5v-IANo=V^ZZx0D~{C0g%V!@`4 zh6~@Q;n*+35Pm})VIIC_EEr?r9|nwVuG*2nm4yZ zt;i#)UK1o{(ZAm2D%J5;eJOyekbH~8%@I)O*7rXxlU+@f+6}s2y?-fT0jQ`RzR8mr z#9tuL7P`F2U~xZ6%_!L4F1}9AJ7Y?h^j33o4M9;)KuG*pS&w7S+X**4hFtMBQxP}_ zt^Ju8H4P0#Q%k`_*C2`l*b=9)3Zw`549+9NOy1wv-S2#~o=EA4H}(zJ(V|M2pJab` z#<5L;W8g6l8=SK(Az~F?j`LIW3BRbhn;84n?Kx^}=GZ+!<5DFn&DUJ*^!cnd@lxX) zzLFAeQrL_!6~c{Fu326uVRkfBR>#_U%zg!@GIyp-5gN8iHO7df5BE=FT~$uFZ_h?p z$pq3@77EhG1?|1`tG^1`s1qL+5$Cb(641r_(5Il98YvXAVsv1Ppo}Z6%}%({+<6!? zOS8v+xqX8!M9-C?se#oJ(e9GSsh(i!N~wyVPd0L_U7UwBxE;!R7C~}r^IC(3e;)99 zx`dT*;WhL5IDKhKYAn?ThZ!Y&AKd_J<9K1_uN9MKH;tt!K?(QgO6>uL)Ej05XDHH$1geTdVX1Ryk~ zqZ)8wqXt9=jh*c!vi>hgA?doSrN%@)7;kk&BT3837Q4!Qv{WpGjodGfb?$YS-(=FZ zmtd3Bej0+Ou;EJX)y(q!`BldUWu3dU3J7=qfHTh$AE({++5`9*@L@*Q*1dQSG^+yg zU`gwE$viCPtaF-rwBIQMRugrxy<1F?);BgHDm~-XX*yMt&&2Uq-W!+LwY~W9{xF>t zpOy*R@#k1ZQpWA-(n=Er6aoI?($9%}U4g}<4sYBg{<4CD5RmK`;lBov6C_w*cb5k~ zFThw}Qb6jPm37^#Y%1M_@nxF<#wN?8ed=`=z9He1- zZE&wsh7V(lv}ZtL+{G@h*G9W`&Ow6nvWd0tF2DgM!II3u+b0I;v0xuN*8BMR&jXm( zCl87kwO9UNR&A9HEr&<4DtsA+^8Lea&Rl!C)PN%U&R-yYu&Lq_QDF5h(S|5?dN`7} zVH*99^g`_38f#iOZdmDS>6J64nv|u3tV-nO-839!b5A{;<@X7%vw08Yd&DRbsZ>uz ze%p*FWag5_IZB@>9gGbizPv8wU@vy~x;s3Et;Y;?<&G~sGwI`yGq+?o71R*&T5|P9 zc;DY*<~lHGJ(Xm{DLtr@=RBy`ly6aw=mF@u)p*N(CK1MJ1xKsxqfTf&U@!!-+1O&J z46z}Qhun@fCr>%VQ4IK%-$Oz961==EFByYR=cO9QC;=N*WPUMyHkHavDP7t;25TL% z7r8rea6X$S-3&&L!Tl$qA((aYo3B;X0C0n;{kQRj(gj zex8h-@|xl67YD4^x*3N7h6X;_3(l+Z$^<5F-?I%d7L_tw#X4xFh{w}WTSV!jGh0z; zYPvC;m|`G?s);u<5YIg3Kla|J_VKOA!f`}EVA&*MuK6j?;sllSZ}#vJpIHwmbfhz} zPr9VFu@##IcYJ&~kEM}drxB4 zrWF9|&VHj4Pt_Q}6zLMS-$l2jLosYRejHRA|0&SEAYxt_UQ45+Ga=67{b20g1P^?8 z(mqVBnze z*G|b7IIMS!jUP6^Be7`u_Gkc|Ew6EQ-kq}gd{>m#8g`J#3mBq$Hy56Qkua%c_#+mO zE8TjJmYzvTHC#Wm^imbuZ*rX2Av$JPPCl!BQ);C03@|`vF;he5A{9gNP4Ls1uHtkW z^kO3?I2dMSHTj(UsBGqOE8fLB^(x0GT;`#!)ZJZ7vT$u|?$Jm!>;$>*$srB|eOf<^ zno$|{$+HH7hOaZTsp-9sc;z@helif$Vexk7qt%|!x1XJy-CJHgD=RRae$JTLzFzS$ zZM4&Cqw*q#;no$*d-tZ4t@E6;XZ0+#fQ8TPd13^An$rtv3%8%1Mdyk=pPQ$c<`fVl z-n*T&K*6+G!`uk#oR83Bax(UvPtxtmQvZjm89IH!)g0$EocYDo+`CO^Mw8Ox;~%e^ zcd@FjICWFu)d87`V(45

  • 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

    T^3Lr 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 CQ%)NI literal 0 HcmV?d00001 diff --git a/miscale/Xiaomi_Scale_Body_Metrics.py b/miscale/Xiaomi_Scale_Body_Metrics.py new file mode 100644 index 0000000..39b846a --- /dev/null +++ b/miscale/Xiaomi_Scale_Body_Metrics.py @@ -0,0 +1,230 @@ +#!/usr/bin/python3 + +# Version info +# =========================================================== +# Export 2 Garmin Connect v2.0 (Xiaomi_Scale_Body_Metrics.py) +# =========================================================== + +from math import floor +import sys +from body_scales import bodyScales + +class bodyMetrics: + def __init__(self, weight, height, age, sex, impedance): + self.weight = weight + self.height = height + self.age = age + self.sex = sex + self.impedance = impedance + self.scales = bodyScales(age, height, sex, weight) + + # Check for potential out of boundaries + if self.height > 220: + print("Height is too high (limit: >220cm) or scale is sleeping") + sys.stderr.write('Height is over 220cm\n') + exit() + elif weight < 10 or weight > 200: + print("Weight is either too low or too high (limits: <10kg and >200kg)") + sys.stderr.write('Weight is below 10kg or above 200kg\n') + exit() + elif age > 99: + print("Age is too high (limit >99 years)") + sys.stderr.write('Age is above 99 years\n') + exit() + elif impedance > 3000: + print("Impedance is above 3000 Ohm") + sys.stderr.write('Impedance is above 3000 Ohm\n') + exit() + + # Set the value to a boundary if it overflows + def checkValueOverflow(self, value, minimum, maximum): + if value < minimum: + return minimum + elif value > maximum: + return maximum + else: + return value + + # Get LBM coefficient (with impedance) + def getLBMCoefficient(self): + lbm = (self.height * 9.058 / 100) * (self.height / 100) + lbm += self.weight * 0.32 + 12.226 + lbm -= self.impedance * 0.0068 + lbm -= self.age * 0.0542 + return lbm + + # Get BMR + def getBMR(self): + if self.sex == 'female': + bmr = 864.6 + self.weight * 10.2036 + bmr -= self.height * 0.39336 + bmr -= self.age * 6.204 + else: + bmr = 877.8 + self.weight * 14.916 + bmr -= self.height * 0.726 + bmr -= self.age * 8.976 + + # Capping + if self.sex == 'female' and bmr > 2996: + bmr = 5000 + elif self.sex == 'male' and bmr > 2322: + bmr = 5000 + return self.checkValueOverflow(bmr, 500, 10000) + + # Get fat percentage + def getFatPercentage(self): + # Set a constant to remove from LBM + if self.sex == 'female' and self.age <= 49: + const = 9.25 + elif self.sex == 'female' and self.age > 49: + const = 7.25 + else: + const = 0.8 + + # Calculate body fat percentage + LBM = self.getLBMCoefficient() + + if self.sex == 'male' and self.weight < 61: + coefficient = 0.98 + elif self.sex == 'female' and self.weight > 60: + coefficient = 0.96 + if self.height > 160: + coefficient *= 1.03 + elif self.sex == 'female' and self.weight < 50: + coefficient = 1.02 + if self.height > 160: + coefficient *= 1.03 + else: + coefficient = 1.0 + fatPercentage = (1.0 - (((LBM - const) * coefficient) / self.weight)) * 100 + + # Capping body fat percentage + if fatPercentage > 63: + fatPercentage = 75 + return self.checkValueOverflow(fatPercentage, 5, 75) + + # Get water percentage + def getWaterPercentage(self): + waterPercentage = (100 - self.getFatPercentage()) * 0.7 + + if (waterPercentage <= 50): + coefficient = 1.02 + else: + coefficient = 0.98 + + # Capping water percentage + if waterPercentage * coefficient >= 65: + waterPercentage = 75 + return self.checkValueOverflow(waterPercentage * coefficient, 35, 75) + + # Get bone mass + def getBoneMass(self): + if self.sex == 'female': + base = 0.245691014 + else: + base = 0.18016894 + + boneMass = (base - (self.getLBMCoefficient() * 0.05158)) * -1 + + if boneMass > 2.2: + boneMass += 0.1 + else: + boneMass -= 0.1 + + # Capping boneMass + if self.sex == 'female' and boneMass > 5.1: + boneMass = 8 + elif self.sex == 'male' and boneMass > 5.2: + boneMass = 8 + return self.checkValueOverflow(boneMass, 0.5 , 8) + + # Get muscle mass + def getMuscleMass(self): + muscleMass = self.weight - ((self.getFatPercentage() * 0.01) * self.weight) - self.getBoneMass() + + # Capping muscle mass + if self.sex == 'female' and muscleMass >= 84: + muscleMass = 120 + elif self.sex == 'male' and muscleMass >= 93.5: + muscleMass = 120 + + return self.checkValueOverflow(muscleMass, 10 ,120) + + # Get Visceral Fat + def getVisceralFat(self): + if self.sex == 'female': + if self.weight > (13 - (self.height * 0.5)) * -1: + subsubcalc = ((self.height * 1.45) + (self.height * 0.1158) * self.height) - 120 + subcalc = self.weight * 500 / subsubcalc + vfal = (subcalc - 6) + (self.age * 0.07) + else: + subcalc = 0.691 + (self.height * -0.0024) + (self.height * -0.0024) + vfal = (((self.height * 0.027) - (subcalc * self.weight)) * -1) + (self.age * 0.07) - self.age + else: + if self.height < self.weight * 1.6: + subcalc = ((self.height * 0.4) - (self.height * (self.height * 0.0826))) * -1 + vfal = ((self.weight * 305) / (subcalc + 48)) - 2.9 + (self.age * 0.15) + else: + subcalc = 0.765 + self.height * -0.0015 + vfal = (((self.height * 0.143) - (self.weight * subcalc)) * -1) + (self.age * 0.15) - 5.0 + + return self.checkValueOverflow(vfal, 1 ,50) + + # Get BMI + def getBMI(self): + return self.checkValueOverflow(self.weight/((self.height/100)*(self.height/100)), 10, 90) + + # Get ideal weight (just doing a reverse BMI, should be something better) + def getIdealWeight(self, orig=True): + # Uses mi fit algorithm (or holtek's one) + if orig and self.sex == 'female': + return (self.height - 70) * 0.6 + elif orig and self.sex == 'male': + return (self.height - 80) * 0.7 + else: + return self.checkValueOverflow((22*self.height)*self.height/10000, 5.5, 198) + + # Get fat mass to ideal (guessing mi fit formula) + def getFatMassToIdeal(self): + mass = (self.weight * (self.getFatPercentage() / 100)) - (self.weight * (self.scales.getFatPercentageScale()[2] / 100)) + if mass < 0: + return f"to_gain:{mass*-1:.1f}" + else: + return f"to_lose:{mass:.1f}" + + # Get protetin percentage (warn: guessed formula) + def getProteinPercentage(self, orig=True): + # Use original algorithm from mi fit (or legacy guess one) + if orig: + proteinPercentage = (self.getMuscleMass() / self.weight) * 100 + proteinPercentage -= self.getWaterPercentage() + else: + proteinPercentage = 100 - (floor(self.getFatPercentage() * 100) / 100) + proteinPercentage -= floor(self.getWaterPercentage() * 100) / 100 + proteinPercentage -= floor((self.getBoneMass()/self.weight*100) * 100) / 100 + + return self.checkValueOverflow(proteinPercentage, 5, 32) + + # Get body type (out of nine possible) + def getBodyType(self): + if self.getFatPercentage() > self.scales.getFatPercentageScale()[2]: + factor = 0 + elif self.getFatPercentage() < self.scales.getFatPercentageScale()[1]: + factor = 2 + else: + factor = 1 + + if self.getMuscleMass() > self.scales.getMuscleMassScale()[1]: + return 3 + (factor * 3) + elif self.getMuscleMass() < self.scales.getMuscleMassScale()[0]: + return 1 + (factor * 3) + else: + return 2 + (factor * 3) + + # Get Metabolic Age + def getMetabolicAge(self): + if self.sex == 'female': + metabolicAge = (self.height * -1.1165) + (self.weight * 1.5784) + (self.age * 0.4615) + (self.impedance * 0.0415) + 83.2548 + else: + metabolicAge = (self.height * -0.7471) + (self.weight * 0.9161) + (self.age * 0.4184) + (self.impedance * 0.0517) + 54.2267 + return self.checkValueOverflow(metabolicAge, 15, 80) diff --git a/miscale/body_scales.py b/miscale/body_scales.py new file mode 100644 index 0000000..6363e5b --- /dev/null +++ b/miscale/body_scales.py @@ -0,0 +1,157 @@ +#!/usr/bin/python3 + +class bodyScales: + def __init__(self, age, height, sex, weight, scaleType='xiaomi'): + self.age = age + self.height = height + self.sex = sex + self.weight = weight + + if scaleType == 'xiaomi': + self.scaleType = 'xiaomi' + else: + self.scaleType = 'holtek' + + # Get BMI scale + def getBMIScale(self): + if self.scaleType == 'xiaomi': + # Amazfit/new mi fit + #return [18.5, 24, 28] + # Old mi fit // amazfit for body figure + return [18.5, 25.0, 28.0, 32.0] + elif self.scaleType == 'holtek': + return [18.5, 25.0, 30.0] + + # Get fat percentage scale + def getFatPercentageScale(self): + # The included tables where quite strange, maybe bogus, replaced them with better ones... + if self.scaleType == 'xiaomi': + scales = [ + {'min': 0, 'max': 12, 'female': [12.0, 21.0, 30.0, 34.0], 'male': [7.0, 16.0, 25.0, 30.0]}, + {'min': 12, 'max': 14, 'female': [15.0, 24.0, 33.0, 37.0], 'male': [7.0, 16.0, 25.0, 30.0]}, + {'min': 14, 'max': 16, 'female': [18.0, 27.0, 36.0, 40.0], 'male': [7.0, 16.0, 25.0, 30.0]}, + {'min': 16, 'max': 18, 'female': [20.0, 28.0, 37.0, 41.0], 'male': [7.0, 16.0, 25.0, 30.0]}, + {'min': 18, 'max': 40, 'female': [21.0, 28.0, 35.0, 40.0], 'male': [11.0, 17.0, 22.0, 27.0]}, + {'min': 40, 'max': 60, 'female': [22.0, 29.0, 36.0, 41.0], 'male': [12.0, 18.0, 23.0, 28.0]}, + {'min': 60, 'max': 100, 'female': [23.0, 30.0, 37.0, 42.0], 'male': [14.0, 20.0, 25.0, 30.0]}, + ] + + elif self.scaleType == 'holtek': + scales = [ + {'min': 0, 'max': 21, 'female': [18, 23, 30, 35], 'male': [8, 14, 21, 25]}, + {'min': 21, 'max': 26, 'female': [19, 24, 30, 35], 'male': [10, 15, 22, 26]}, + {'min': 26, 'max': 31, 'female': [20, 25, 31, 36], 'male': [11, 16, 21, 27]}, + {'min': 31, 'max': 36, 'female': [21, 26, 33, 36], 'male': [13, 17, 25, 28]}, + {'min': 36, 'max': 41, 'female': [22, 27, 34, 37], 'male': [15, 20, 26, 29]}, + {'min': 41, 'max': 46, 'female': [23, 28, 35, 38], 'male': [16, 22, 27, 30]}, + {'min': 46, 'max': 51, 'female': [24, 30, 36, 38], 'male': [17, 23, 29, 31]}, + {'min': 51, 'max': 56, 'female': [26, 31, 36, 39], 'male': [19, 25, 30, 33]}, + {'min': 56, 'max': 100, 'female': [27, 32, 37, 40], 'male': [21, 26, 31, 34]}, + ] + + for scale in scales: + if self.age >= scale['min'] and self.age < scale['max']: + return scale[self.sex] + + # Get muscle mass scale + def getMuscleMassScale(self): + if self.scaleType == 'xiaomi': + scales = [ + {'min': {'male': 170, 'female': 160}, 'female': [36.5, 42.6], 'male': [49.4, 59.5]}, + {'min': {'male': 160, 'female': 150}, 'female': [32.9, 37.6], 'male': [44.0, 52.5]}, + {'min': {'male': 0, 'female': 0}, 'female': [29.1, 34.8], 'male': [38.5, 46.6]}, + ] + elif self.scaleType == 'holtek': + scales = [ + {'min': {'male': 170, 'female': 170}, 'female': [36.5, 42.5], 'male': [49.5, 59.4]}, + {'min': {'male': 160, 'female': 160}, 'female': [32.9, 37.5], 'male': [44.0, 52.4]}, + {'min': {'male': 0, 'female': 0}, 'female': [29.1, 34.7], 'male': [38.5, 46.5]} + ] + + for scale in scales: + if self.height >= scale['min'][self.sex]: + return scale[self.sex] + + + + # Get water percentage scale + def getWaterPercentageScale(self): + if self.scaleType == 'xiaomi': + if self.sex == 'male': + return [55.0, 65.1] + elif self.sex == 'female': + return [45.0, 60.1] + elif self.scaleType == 'holtek': + return [53, 67] + + + # Get visceral fat scale + def getVisceralFatScale(self): + # Actually the same in mi fit/amazfit and holtek's sdk + return [10.0, 15.0] + + + # Get bone mass scale + def getBoneMassScale(self): + if self.scaleType == 'xiaomi': + scales = [ + {'male': {'min': 75.0, 'scale': [2.0, 4.2]}, 'female': {'min': 60.0, 'scale': [1.8, 3.9]}}, + {'male': {'min': 60.0, 'scale': [1.9, 4.1]}, 'female': {'min': 45.0, 'scale': [1.5, 3.8]}}, + {'male': {'min': 0.0, 'scale': [1.6, 3.9]}, 'female': {'min': 0.0, 'scale': [1.3, 3.6]}}, + ] + + for scale in scales: + if self.weight >= scale[self.sex]['min']: + return scale[self.sex]['scale'] + + elif self.scaleType == 'holtek': + scales = [ + {'female': {'min': 60, 'optimal': 2.5}, 'male': {'min': 75, 'optimal': 3.2}}, + {'female': {'min': 45, 'optimal': 2.2}, 'male': {'min': 69, 'optimal': 2.9}}, + {'female': {'min': 0, 'optimal': 1.8}, 'male': {'min': 0, 'optimal': 2.5}} + ] + + for scale in scales: + if self.weight >= scale[self.sex]['min']: + return [scale[self.sex]['optimal']-1, scale[self.sex]['optimal']+1] + + + # Get BMR scale + def getBMRScale(self): + if self.scaleType == 'xiaomi': + coefficients = { + 'male': {30: 21.6, 50: 20.07, 100: 19.35}, + 'female': {30: 21.24, 50: 19.53, 100: 18.63} + } + elif self.scaleType == 'holtek': + coefficients = { + 'female': {12: 34, 15: 29, 17: 24, 29: 22, 50: 20, 120: 19}, + 'male': {12: 36, 15: 30, 17: 26, 29: 23, 50: 21, 120: 20} + } + + for age, coefficient in coefficients[self.sex].items(): + if self.age < age: + return [self.weight * coefficient] + + + # Get protein scale (hardcoded in mi fit) + def getProteinPercentageScale(self): + # Actually the same in mi fit and holtek's sdk + return [16, 20] + + # Get ideal weight scale (BMI scale converted to weights) + def getIdealWeightScale(self): + scale = [] + for bmiScale in self.getBMIScale(): + scale.append((bmiScale*self.height)*self.height/10000) + return scale + + # Get Body Score scale + def getBodyScoreScale(self): + # very bad, bad, normal, good, better + return [50.0, 60.0, 80.0, 90.0] + + # Return body type scale + def getBodyTypeScale(self): + return ['obese', 'overweight', 'thick-set', 'lack-exerscise', 'balanced', 'balanced-muscular', 'skinny', 'balanced-skinny', 'skinny-muscular'] + diff --git a/miscale/miscale_ble.py b/miscale/miscale_ble.py new file mode 100644 index 0000000..15996b6 --- /dev/null +++ b/miscale/miscale_ble.py @@ -0,0 +1,148 @@ +#!/usr/bin/python3 + +import os +import time +from datetime import datetime as dt +from bluepy import btle + +# Version info +print(""" +============================================= +Export 2 Garmin Connect v2.2 (miscale_ble.py) +============================================= +""") + +# Importing bluetooth variables from a file +path = os.path.dirname(os.path.dirname(__file__)) +with open(path + '/user/export2garmin.cfg', 'r') as file: + for line in file: + line = line.strip() + if line.startswith('ble_'): + name, value = line.split('=') + globals()[name.strip()] = value.strip() + +# Reading data from a scale using a BLE adapter +class miScale(btle.DefaultDelegate): + def __init__(self): + btle.DefaultDelegate.__init__(self) + self.address = ble_miscale_mac.lower() + self.unique_dev_addresses = [] + self.ble_adapter_time = int(ble_adapter_time) + self.ble_adapter_repeat = int(ble_adapter_repeat) + def handleDiscovery(self, dev, isNewDev, isNewData): + if dev.addr not in self.unique_dev_addresses: + self.unique_dev_addresses.append(dev.addr) + print(f" BLE device found with address: {dev.addr}" + (" <= target device" if dev.addr == self.address else ", non-target device")) + if dev.addr == self.address: + for (adType, desc, value) in dev.getScanData(): + if adType == 22: + data = bytes.fromhex(value[4:]) + ctrlByte1 = data[1] + hasImpedance = ctrlByte1 & (1<<1) + if hasImpedance: + + # lbs to kg unit conversion + if value[4:6] == '03': + lb_weight = int((value[28:30] + value[26:28]), 16) * 0.01 + weight = round(lb_weight / 2.2046, 1) + else: + weight = (((data[12] & 0xFF) << 8) | (data[11] & 0xFF)) * 0.005 + impedance = ((data[10] & 0xFF) << 8) | (data[9] & 0xFF) + unix_time = int(dt.timestamp(dt.strptime(f"{int((data[3] << 8) | data[2])},{int(data[4])},{int(data[5])},{int(data[6])},{int(data[7])},{int(data[8])}","%Y,%m,%d,%H,%M,%S"))) + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * Reading BLE data complete, finished BLE scan") + print(f"{unix_time};{weight:.1f};{impedance:.0f}") + else: + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * Reading BLE data incomplete, finished BLE scan") + exit() + def run(self): + + # Verifying correct working of BLE adapter, max 3 times + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * Checking if a BLE adapter is detected") + ble_error = 0 + ble_success = False + while ble_error < 3: + ble_error += 1 + if ble_adapter_switch == "on": + if not os.popen(f"hcitool dev | awk '/{ble_adapter_mac}/ {{print $1}}'").read(): + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * BLE adapter {ble_adapter_mac} not detected, restarting bluetooth service") + else: + ble_adapter_hci_read = os.popen(f"hcitool dev | awk '/{ble_adapter_mac}/ {{print $1}}' | cut -c4").read().strip() + ble_adapter_mac_read = ble_adapter_mac + ble_success = True + else: + if not os.popen(f"hcitool dev | awk '/hci{ble_adapter_hci}/ {{print $2}}'").read(): + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * BLE adapter hci{ble_adapter_hci} not detected, restarting bluetooth service") + else: + ble_adapter_hci_read = ble_adapter_hci + ble_adapter_mac_read = os.popen(f"hcitool dev | awk '/hci{ble_adapter_hci}/ {{print $2}}'").read().strip() + ble_success = True + + if ble_success == False: + os.system("sudo modprobe btusb >/dev/null 2>&1") + time.sleep(1) + os.system("sudo systemctl restart bluetooth.service >/dev/null 2>&1") + time.sleep(1) + else: + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * BLE adapter hci{ble_adapter_hci_read}({ble_adapter_mac_read}) detected, check BLE adapter connection") + break + + if ble_error == 3 and not ble_success: + if ble_adapter_switch == "on": + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * BLE adapter {ble_adapter_mac} failed to be found, not detected by {ble_error} attempts") + else: + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * BLE adapter hci{ble_adapter_hci} failed to be found, not detected by {ble_error} attempts") + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * Finished BLE scan") + return + + # Verifying correct working of BLE adapter connection, max 3 times + con_error = 0 + con_success = False + while con_error < 3 and ble_success: + con_error += 1 + try: + scanner = btle.Scanner(ble_adapter_hci_read) + scanner.withDelegate(self) + scanner.start() + scanner.stop() + con_success = True + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * Connection to BLE adapter hci{ble_adapter_hci_read}({ble_adapter_mac_read}) works, starting BLE scan:") + break + except btle.BTLEManagementError: + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * Connection error, restarting BLE adapter hci{ble_adapter_hci_read}({ble_adapter_mac_read})") + os.system(f"sudo hciconfig hci{ble_adapter_hci_read} down >/dev/null 2>&1") + time.sleep(1) + os.system(f"sudo hciconfig hci{ble_adapter_hci_read} up >/dev/null 2>&1") + time.sleep(1) + if con_error == 3 and not con_success: + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * Failed to connect to BLE adapter hci{ble_adapter_hci_read}({ble_adapter_mac_read}) by {con_error} attempts") + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * Finished BLE scan") + return + + # Scanning for BLE devices in range + dev_around = 0 + while ble_success and con_success: + scanner.start() + scanner.process(self.ble_adapter_time) + scanner.stop() + if ble_adapter_check == "on" and not self.unique_dev_addresses: + dev_around += 1 + if dev_around < self.ble_adapter_repeat: + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * No devices around") + elif dev_around == self.ble_adapter_repeat: + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * No devices around, restarting bluetooth service") + os.system("sudo modprobe btusb >/dev/null 2>&1") + time.sleep(1) + os.system("sudo systemctl restart bluetooth.service >/dev/null 2>&1") + time.sleep(1) + else: + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * No devices around, failed {dev_around} attempts") + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * Finished BLE scan") + break + else: + print(f"{dt.now().strftime('%d.%m.%Y-%H:%M:%S')} * Finished BLE scan") + break + +# Main program loop +if __name__ == "__main__": + scale = miScale() + scale.run() \ No newline at end of file diff --git a/miscale/miscale_esp32.ino b/miscale/miscale_esp32.ino new file mode 100644 index 0000000..4d6572b --- /dev/null +++ b/miscale/miscale_esp32.ino @@ -0,0 +1,215 @@ +// WARNING use Arduino ESP32 library version 1.0.4, newer is unstable +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Scale MAC address, please use lowercase letters +#define scale_mac_addr "00:00:00:00:00:00" + +// Network details +const char* ssid = "ssid_name"; +const char* password = "password"; + +// Synchronization status LED, for LOLIN32 D32 PRO is pin 5 +const int led_pin = 5; + +// Instantiating object of class Timestamp (time offset is possible in import_data.sh file) +Timestamps ts(0); + +// Battery voltage measurement, for LOLIN32 D32 PRO is pin 35 +Battery18650Stats battery(35); + +// MQTT details +const char* mqtt_server = "ip_address"; +const int mqtt_port = 1883; +const char* mqtt_userName = "admin"; +const char* mqtt_userPass = "user_password"; +const char* clientId = "esp32_scale"; +const char* mqtt_attributes = "data"; + +String mqtt_clientId = String(clientId); +String mqtt_topic_attributes = String(mqtt_attributes); +String publish_data; + +WiFiClient espClient; +PubSubClient mqtt_client(espClient); + +int16_t stoi(String input, uint16_t index1) { + return (int16_t)(strtol(input.substring(index1, index1+2).c_str(), NULL, 16)); +} +int16_t stoi2(String input, uint16_t index1) { + return (int16_t)(strtol((input.substring(index1+2, index1+4) + input.substring(index1, index1+2)).c_str(), NULL, 16)); +} + +void goToDeepSleep() { + // Deep sleep for 7 minutes + Serial.println("* Waiting for next scan, going to sleep"); + esp_sleep_enable_timer_wakeup(7 * 60 * 1000000); + esp_deep_sleep_start(); +} + +void StartESP32() { + // LED indicate start ESP32, is on for 0.25 second + pinMode(led_pin, OUTPUT); + digitalWrite(led_pin, LOW); + delay(250); + digitalWrite(led_pin, HIGH); + + // Initializing serial port for debugging purposes, version info + Serial.begin(115200); + Serial.println(); + Serial.println("================================================"); + Serial.println("Export 2 Garmin Connect v2.0 (miscale_esp32.ino)"); + Serial.println("================================================"); + Serial.println(); +} + +void errorLED_connect() { + pinMode(led_pin, OUTPUT); + digitalWrite(led_pin, LOW); + delay(5000); + Serial.println("failed"); + goToDeepSleep(); +} + +void connectWiFi() { + int nFailCount = 0; + Serial.print("* Connecting to WiFi: "); + while (WiFi.status() != WL_CONNECTED) { + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + WiFi.waitForConnectResult(); + if (WiFi.status() == WL_CONNECTED) { + Serial.println("connected"); + Serial.print(" IP address: "); + Serial.println(WiFi.localIP()); + } + else { + Serial.print("."); + delay(200); + nFailCount++; + if (nFailCount > 75) + errorLED_connect(); + } + } +} + +void connectMQTT() { + int nFailCount = 0; + connectWiFi(); + Serial.print("* Connecting to MQTT: "); + while (!mqtt_client.connected()) { + mqtt_client.setServer(mqtt_server, mqtt_port); + if (mqtt_client.connect(mqtt_clientId.c_str(),mqtt_userName,mqtt_userPass)) { + Serial.println("connected"); + } + else { + Serial.print("."); + delay(200); + nFailCount++; + if (nFailCount > 75) + errorLED_connect(); + } + } +} + +class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks { + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print(" BLE device found with address: "); + Serial.print(advertisedDevice.getAddress().toString().c_str()); + if (advertisedDevice.getAddress().toString() == scale_mac_addr) { + Serial.println(" <= target device"); + BLEScan *pBLEScan = BLEDevice::getScan(); // found what we want, stop now + pBLEScan->stop(); + } + else { + Serial.println(", non-target device"); + } + } +}; + +void errorLED_scan() { + pinMode(led_pin, OUTPUT); + digitalWrite(led_pin, LOW); + delay(5000); + Serial.println("* Reading BLE data incomplete, finished BLE scan"); + goToDeepSleep(); +} + +void ScanBLE() { + Serial.println("* Starting BLE scan:"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); //Create new scan. + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(false); //Active scan uses more power. + pBLEScan->setInterval(0x50); + pBLEScan->setWindow(0x30); + + // Scan for 10 seconds + BLEScanResults foundDevices = pBLEScan->start(10); + int count = foundDevices.getCount(); + for (int i = 0; i < count; i++) { + BLEAdvertisedDevice d = foundDevices.getDevice(i); + if (d.getAddress().toString() != scale_mac_addr) + continue; + String hex; + if (d.haveServiceData()) { + std::string md = d.getServiceData(); + uint8_t* mdp = (uint8_t*)d.getServiceData().data(); + char *pHex = BLEUtils::buildHexData(nullptr, mdp, md.length()); + hex = pHex; + free(pHex); + } + float Weight = stoi2(hex, 22) * 0.005; + float Impedance = stoi2(hex, 18); + if (Impedance > 0) { + int Unix_time = ts.getTimestampUNIX(stoi2(hex, 4), stoi(hex, 8), stoi(hex, 10), stoi(hex, 12), stoi(hex, 14), stoi(hex, 16)); + + // LED blinking for 0.75 second, indicate finish reading BLE data + Serial.println("* Reading BLE data complete, finished BLE scan"); + digitalWrite(led_pin, LOW); + delay(250); + digitalWrite(led_pin, HIGH); + delay(250); + digitalWrite(led_pin, LOW); + delay(250); + digitalWrite(led_pin, HIGH); + + // Prepare to send raw values + publish_data += String(Unix_time); + publish_data += String(";"); + publish_data += String(Weight, 1); + publish_data += String(";"); + publish_data += String(Impedance, 0); + publish_data += String(";"); + publish_data += String(battery.getBatteryVolts(), 1); + publish_data += String(";"); + publish_data += String(battery.getBatteryChargeLevel()); + + // Send data to MQTT broker and let app figure out the rest + connectMQTT(); + mqtt_client.publish(mqtt_topic_attributes.c_str(), publish_data.c_str(), true); + Serial.print("* Publishing MQTT data: "); + Serial.println(publish_data.c_str()); + } + else { + errorLED_scan(); + } + } +} + +void setup() { + StartESP32(); + ScanBLE(); + goToDeepSleep(); +} + +void loop() { +} \ No newline at end of file diff --git a/miscale/miscale_export.py b/miscale/miscale_export.py new file mode 100644 index 0000000..137ce95 --- /dev/null +++ b/miscale/miscale_export.py @@ -0,0 +1,90 @@ +#!/usr/bin/python3 + +import os +import csv +import Xiaomi_Scale_Body_Metrics +from datetime import datetime as dt, date +from garminconnect import Garmin + +# Version info +print(""" +=============================================== +Export 2 Garmin Connect v2.0 (miscale_export.py +=============================================== +""") + +class User(): + def __init__(self, sex, height, birthdate, email, max_weight, min_weight): + self.sex = sex + self.height = height + self.birthdate = birthdate + self.email = email + self.max_weight = max_weight + self.min_weight = min_weight + + # Calculating age + @property + def age(self): + today = date.today() + calc_date = dt.strptime(self.birthdate, "%d-%m-%Y") + return today.year - calc_date.year + +# Importing user variables from a file +path = os.path.dirname(os.path.dirname(__file__)) +users = [] +with open(path + '/user/export2garmin.cfg', 'r') as file: + for line in file: + line = line.strip() + if line.startswith('miscale_export_'): + user_data = eval(line.split('=')[1].strip()) + users.append(User(*user_data)) + +# Import data variables from a file +with open(path + '/user/miscale_backup.csv', 'r') as csv_file: + csv_reader = csv.reader(csv_file, delimiter=';') + for row in csv_reader: + if str(row[0]) in ["failed", "to_import"]: + mitdatetime = int(row[1]) + weight = float(row[2]) + miimpedance = float(row[3]) + break + +# Matching Garmin Connect account to weight +selected_user = None +for user in users: + if user.min_weight <= weight <= user.max_weight: + selected_user = user + break + +# Calcuating body metrics +if selected_user is not None and 'email@email.com' not in selected_user.email: + lib = Xiaomi_Scale_Body_Metrics.bodyMetrics(weight, selected_user.height, selected_user.age, selected_user.sex, int(miimpedance)) + bmi = lib.getBMI() + percent_fat = lib.getFatPercentage() + muscle_mass = lib.getMuscleMass() + bone_mass = lib.getBoneMass() + percent_hydration = lib.getWaterPercentage() + physique_rating = lib.getBodyType() + visceral_fat_rating = lib.getVisceralFat() + metabolic_age = lib.getMetabolicAge() + basal_met = lib.getBMR() + + # Print to temp.log file + formatted_time = dt.fromtimestamp(mitdatetime).strftime("%d.%m.%Y;%H:%M") + print(f"MISCALE * Import data: {mitdatetime};{weight:.1f};{miimpedance:.0f}") + print(f"MISCALE * Calculated data: {formatted_time};{weight:.1f};{bmi:.1f};{percent_fat:.1f};{muscle_mass:.1f};{bone_mass:.1f};{percent_hydration:.1f};{physique_rating:.0f};{visceral_fat_rating:.0f};{metabolic_age:.0f};{basal_met:.0f};{lib.getLBMCoefficient():.1f};{lib.getIdealWeight():.1f};{lib.getFatMassToIdeal()};{lib.getProteinPercentage():.1f};{miimpedance:.0f};{selected_user.email};{dt.now().strftime('%d.%m.%Y;%H:%M')}") + + # Login to Garmin Connect + with open(path + '/user/' + selected_user.email, 'r') as token_file: + tokenstore = token_file.read() + garmin = Garmin() + garmin.login(tokenstore) + + # Upload data to Garmin Connect + garmin.add_body_composition(dt.fromtimestamp(mitdatetime).isoformat(),weight=weight,bmi=bmi,percent_fat=percent_fat,muscle_mass=muscle_mass,bone_mass=bone_mass,percent_hydration=percent_hydration,physique_rating=physique_rating,visceral_fat_rating=visceral_fat_rating,metabolic_age=metabolic_age,basal_met=basal_met) + print("MISCALE * Upload status: OK") +else: + + # Print to temp.log file + print(f"MISCALE * Import data: {mitdatetime};{weight:.1f};{miimpedance:.0f}") + print("MISCALE * There is no user with given weight or undefined user email@email.com, check users section in export2garmin.cfg") \ No newline at end of file diff --git a/omron/deviceSpecific/hem-6232t.py b/omron/deviceSpecific/hem-6232t.py new file mode 100644 index 0000000..d90092b --- /dev/null +++ b/omron/deviceSpecific/hem-6232t.py @@ -0,0 +1,59 @@ +import sys +import datetime +import logging +logger = logging.getLogger("omblepy") + +sys.path.append('..') +from sharedDriver import sharedDeviceDriverCode + +class deviceSpecificDriver(sharedDeviceDriverCode): + deviceEndianess = "big" + userStartAdressesList = [0x2e8, 0x860] + perUserRecordsCountList = [100 , 100 ] + recordByteSize = 0x0e + transmissionBlockSize = 0x38 + + settingsReadAddress = 0x0260 + settingsWriteAddress = 0x02A4 + + settingsUnreadRecordsBytes = [0x00, 0x08] + settingsTimeSyncBytes = [0x14, 0x1e] #this is probably not correct + + def deviceSpecific_ParseRecordFormat(self, singleRecordAsByteArray): + recordDict = dict() + recordDict["dia"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 0, 7) + recordDict["sys"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 8, 15) + 25 + year = self._bytearrayBitsToInt(singleRecordAsByteArray, 18, 23) + 2000 + recordDict["bpm"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 24, 31) + recordDict["ihb"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 32, 32) + recordDict["mov"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 33, 33) + month = self._bytearrayBitsToInt(singleRecordAsByteArray, 34, 37) + day = self._bytearrayBitsToInt(singleRecordAsByteArray, 38, 42) + hour = self._bytearrayBitsToInt(singleRecordAsByteArray, 43, 47) + minute = self._bytearrayBitsToInt(singleRecordAsByteArray, 52, 57) + second = self._bytearrayBitsToInt(singleRecordAsByteArray, 58, 63) + second = min([second, 59]) #for some reason the second value can range up to 63 + recordDict["datetime"] = datetime.datetime(year, month, day, hour, minute, second) + return recordDict + + def deviceSpecific_syncWithSystemTime(self): + raise ValueError("Not supported yet.") + """ + timeSyncSettingsCopy = self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] + #read current time from cached settings bytes + month, year, hour, day, second, minute = [int(byte) for byte in timeSyncSettingsCopy[2:8]] + try: + logger.info(f"device is set to date: {datetime.datetime(year + 2000, month, day, hour, minute, second).strftime('%Y-%m-%d %H:%M:%S')}") + except: + logger.warning(f"device is set to an invalid date") + + #write the current time into the cached settings which will be written later + currentTime = datetime.datetime.now() + setNewTimeDataBytes = timeSyncSettingsCopy[0:2] + setNewTimeDataBytes += bytes([currentTime.month, currentTime.year - 2000, currentTime.hour, currentTime.day, currentTime.second, currentTime.minute]) + setNewTimeDataBytes += bytes([0x00, sum(setNewTimeDataBytes) & 0xff]) #first byte does not seem to matter, second byte is crc generated by sum over data and only using lower 8 bits + self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] = setNewTimeDataBytes + + logger.info(f"settings updated to new date {currentTime.strftime('%Y-%m-%d %H:%M:%S')}") + return + """ diff --git a/omron/deviceSpecific/hem-7150t.py b/omron/deviceSpecific/hem-7150t.py new file mode 100644 index 0000000..5a95e96 --- /dev/null +++ b/omron/deviceSpecific/hem-7150t.py @@ -0,0 +1,56 @@ +import sys +import datetime +import logging +logger = logging.getLogger("omblepy") + +sys.path.append('..') +from sharedDriver import sharedDeviceDriverCode + +class deviceSpecificDriver(sharedDeviceDriverCode): + deviceEndianess = "little" + userStartAdressesList = [0x0098] + perUserRecordsCountList = [60] + recordByteSize = 0x10 + transmissionBlockSize = 0x10 + + settingsReadAddress = 0x0010 + settingsWriteAddress = 0x0054 + + settingsUnreadRecordsBytes = [0x00, 0x10] + settingsTimeSyncBytes = [0x2C, 0x3C] + + def deviceSpecific_ParseRecordFormat(self, singleRecordAsByteArray): + recordDict = dict() + minute = self._bytearrayBitsToInt(singleRecordAsByteArray, 68, 73) + second = self._bytearrayBitsToInt(singleRecordAsByteArray, 74, 79) + second = min([second, 59]) #for some reason the second value can range up to 63 + recordDict["mov"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 80, 80) + recordDict["ihb"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 81, 81) + month = self._bytearrayBitsToInt(singleRecordAsByteArray, 82, 85) + day = self._bytearrayBitsToInt(singleRecordAsByteArray, 86, 90) + hour = self._bytearrayBitsToInt(singleRecordAsByteArray, 91, 95) + year = self._bytearrayBitsToInt(singleRecordAsByteArray, 98, 103) + 2000 + recordDict["bpm"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 104, 111) + recordDict["dia"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 112, 119) + recordDict["sys"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 120, 127) + 25 + recordDict["datetime"] = datetime.datetime(year, month, day, hour, minute, second) + return recordDict + + def deviceSpecific_syncWithSystemTime(self): + timeSyncSettingsCopy = self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] + #read current time from cached settings bytes + year, month, day, hour, minute, second = [int(byte) for byte in timeSyncSettingsCopy[8:14]] + try: + logger.info(f"device is set to date: {datetime.datetime(year + 2000, month, day, hour, minute, second).strftime('%Y-%m-%d %H:%M:%S')}") + except: + logger.warning(f"device is set to an invalid date") + + #write the current time into the cached settings which will be written later + currentTime = datetime.datetime.now() + setNewTimeDataBytes = timeSyncSettingsCopy[0:8] #Take the first eight bytes from eeprom without modification + setNewTimeDataBytes += bytes([currentTime.year - 2000, currentTime.month, currentTime.day, currentTime.hour, currentTime.minute, currentTime.second]) + setNewTimeDataBytes += bytes([sum(setNewTimeDataBytes) & 0xff, 0x00]) #first byte does not seem to matter, second byte is crc generated by sum over data and only using lower 8 bits + self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] = setNewTimeDataBytes + + logger.info(f"settings updated to new date {currentTime.strftime('%Y-%m-%d %H:%M:%S')}") + return \ No newline at end of file diff --git a/omron/deviceSpecific/hem-7155t.py b/omron/deviceSpecific/hem-7155t.py new file mode 100644 index 0000000..224eb51 --- /dev/null +++ b/omron/deviceSpecific/hem-7155t.py @@ -0,0 +1,56 @@ +import sys +import datetime +import logging +logger = logging.getLogger("omblepy") + +sys.path.append('..') +from sharedDriver import sharedDeviceDriverCode + +class deviceSpecificDriver(sharedDeviceDriverCode): + deviceEndianess = "little" + userStartAdressesList = [0x0098, 0x0458] + perUserRecordsCountList = [60 , 60 ] + recordByteSize = 0x10 + transmissionBlockSize = 0x10 + + settingsReadAddress = 0x0010 + settingsWriteAddress = 0x0054 + + settingsUnreadRecordsBytes = [0x00, 0x10] + settingsTimeSyncBytes = [0x2C, 0x3C] + + def deviceSpecific_ParseRecordFormat(self, singleRecordAsByteArray): + recordDict = dict() + minute = self._bytearrayBitsToInt(singleRecordAsByteArray, 68, 73) + second = self._bytearrayBitsToInt(singleRecordAsByteArray, 74, 79) + second = min([second, 59]) #for some reason the second value can range up to 63 + recordDict["mov"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 80, 80) + recordDict["ihb"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 81, 81) + month = self._bytearrayBitsToInt(singleRecordAsByteArray, 82, 85) + day = self._bytearrayBitsToInt(singleRecordAsByteArray, 86, 90) + hour = self._bytearrayBitsToInt(singleRecordAsByteArray, 91, 95) + year = self._bytearrayBitsToInt(singleRecordAsByteArray, 98, 103) + 2000 + recordDict["bpm"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 104, 111) + recordDict["dia"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 112, 119) + recordDict["sys"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 120, 127) + 25 + recordDict["datetime"] = datetime.datetime(year, month, day, hour, minute, second) + return recordDict + + def deviceSpecific_syncWithSystemTime(self): + timeSyncSettingsCopy = self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] + #read current time from cached settings bytes + year, month, day, hour, minute, second = [int(byte) for byte in timeSyncSettingsCopy[8:14]] + try: + logger.info(f"device is set to date: {datetime.datetime(year + 2000, month, day, hour, minute, second).strftime('%Y-%m-%d %H:%M:%S')}") + except: + logger.warning(f"device is set to an invalid date") + + #write the current time into the cached settings which will be written later + currentTime = datetime.datetime.now() + setNewTimeDataBytes = timeSyncSettingsCopy[0:8] #Take the first eight bytes from eeprom without modification + setNewTimeDataBytes += bytes([currentTime.year - 2000, currentTime.month, currentTime.day, currentTime.hour, currentTime.minute, currentTime.second]) + setNewTimeDataBytes += bytes([sum(setNewTimeDataBytes) & 0xff, 0x00]) #first byte does not seem to matter, second byte is crc generated by sum over data and only using lower 8 bits + self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] = setNewTimeDataBytes + + logger.info(f"settings updated to new date {currentTime.strftime('%Y-%m-%d %H:%M:%S')}") + return \ No newline at end of file diff --git a/omron/deviceSpecific/hem-7322t.py b/omron/deviceSpecific/hem-7322t.py new file mode 100644 index 0000000..a210af6 --- /dev/null +++ b/omron/deviceSpecific/hem-7322t.py @@ -0,0 +1,57 @@ +import sys +import datetime +import logging +logger = logging.getLogger("omblepy") + +sys.path.append('..') +from sharedDriver import sharedDeviceDriverCode + +class deviceSpecificDriver(sharedDeviceDriverCode): + deviceEndianess = "big" + userStartAdressesList = [0x02ac, 0x0824] + perUserRecordsCountList = [100 , 100 ] + recordByteSize = 0x0e + transmissionBlockSize = 0x38 + + settingsReadAddress = 0x0260 + settingsWriteAddress = 0x0286 + + settingsUnreadRecordsBytes = [0x00, 0x08] + settingsTimeSyncBytes = [0x14, 0x1e] + + def deviceSpecific_ParseRecordFormat(self, singleRecordAsByteArray): + recordDict = dict() + recordDict["dia"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 0, 7) + recordDict["sys"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 8, 15) + 25 + year = self._bytearrayBitsToInt(singleRecordAsByteArray, 16, 23) + 2000 + recordDict["bpm"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 24, 31) + recordDict["mov"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 32, 32) + recordDict["ihb"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 33, 33) + month = self._bytearrayBitsToInt(singleRecordAsByteArray, 34, 37) + day = self._bytearrayBitsToInt(singleRecordAsByteArray, 38, 42) + hour = self._bytearrayBitsToInt(singleRecordAsByteArray, 43, 47) + minute = self._bytearrayBitsToInt(singleRecordAsByteArray, 52, 57) + second = self._bytearrayBitsToInt(singleRecordAsByteArray, 58, 63) + second = min([second, 59]) #for some reason the second value can range up to 63 + recordDict["datetime"] = datetime.datetime(year, month, day, hour, minute, second) + return recordDict + + def deviceSpecific_syncWithSystemTime(self): + + timeSyncSettingsCopy = self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] + #read current time from cached settings bytes + month, year, hour, day, second, minute = [int(byte) for byte in timeSyncSettingsCopy[2:8]] + try: + logger.info(f"device is set to date: {datetime.datetime(year + 2000, month, day, hour, minute, second).strftime('%Y-%m-%d %H:%M:%S')}") + except: + logger.warning(f"device is set to an invalid date") + + #write the current time into the cached settings which will be written later + currentTime = datetime.datetime.now() + setNewTimeDataBytes = timeSyncSettingsCopy[0:2] + setNewTimeDataBytes += bytes([currentTime.month, currentTime.year - 2000, currentTime.hour, currentTime.day, currentTime.second, currentTime.minute]) + setNewTimeDataBytes += bytes([0x00, sum(setNewTimeDataBytes) & 0xff]) #first byte does not seem to matter, second byte is crc generated by sum over data and only using lower 8 bits + self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] = setNewTimeDataBytes + + logger.info(f"settings updated to new date {currentTime.strftime('%Y-%m-%d %H:%M:%S')}") + return \ No newline at end of file diff --git a/omron/deviceSpecific/hem-7342t.py b/omron/deviceSpecific/hem-7342t.py new file mode 100644 index 0000000..4480e55 --- /dev/null +++ b/omron/deviceSpecific/hem-7342t.py @@ -0,0 +1,56 @@ +import sys +import datetime +import logging +logger = logging.getLogger("omblepy") + +sys.path.append('..') +from sharedDriver import sharedDeviceDriverCode + +class deviceSpecificDriver(sharedDeviceDriverCode): + deviceEndianess = "little" + userStartAdressesList = [0x0098, 0x06D8] + perUserRecordsCountList = [100 , 100 ] + recordByteSize = 0x10 + transmissionBlockSize = 0x10 + + settingsReadAddress = 0x0010 + settingsWriteAddress = 0x0054 + + settingsUnreadRecordsBytes = [0x00, 0x10] + settingsTimeSyncBytes = [0x2C, 0x3C] + + def deviceSpecific_ParseRecordFormat(self, singleRecordAsByteArray): + recordDict = dict() + minute = self._bytearrayBitsToInt(singleRecordAsByteArray, 68, 73) + second = self._bytearrayBitsToInt(singleRecordAsByteArray, 74, 79) + second = min([second, 59]) #for some reason the second value can range up to 63 + recordDict["mov"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 80, 80) + recordDict["ihb"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 81, 81) + month = self._bytearrayBitsToInt(singleRecordAsByteArray, 82, 85) + day = self._bytearrayBitsToInt(singleRecordAsByteArray, 86, 90) + hour = self._bytearrayBitsToInt(singleRecordAsByteArray, 91, 95) + year = self._bytearrayBitsToInt(singleRecordAsByteArray, 98, 103) + 2000 + recordDict["bpm"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 104, 111) + recordDict["dia"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 112, 119) + recordDict["sys"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 120, 127) + 25 + recordDict["datetime"] = datetime.datetime(year, month, day, hour, minute, second) + return recordDict + + def deviceSpecific_syncWithSystemTime(self): + timeSyncSettingsCopy = self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] + #read current time from cached settings bytes + year, month, day, hour, minute, second = [int(byte) for byte in timeSyncSettingsCopy[8:14]] + try: + logger.info(f"device is set to date: {datetime.datetime(year + 2000, month, day, hour, minute, second).strftime('%Y-%m-%d %H:%M:%S')}") + except: + logger.warning(f"device is set to an invalid date") + + #write the current time into the cached settings which will be written later + currentTime = datetime.datetime.now() + setNewTimeDataBytes = timeSyncSettingsCopy[0:8] #Take the first eight bytes from eeprom without modification + setNewTimeDataBytes += bytes([currentTime.year - 2000, currentTime.month, currentTime.day, currentTime.hour, currentTime.minute, currentTime.second]) + setNewTimeDataBytes += bytes([sum(setNewTimeDataBytes) & 0xff, 0x00]) #first byte does not seem to matter, second byte is crc generated by sum over data and only using lower 8 bits + self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] = setNewTimeDataBytes + + logger.info(f"settings updated to new date {currentTime.strftime('%Y-%m-%d %H:%M:%S')}") + return \ No newline at end of file diff --git a/omron/deviceSpecific/hem-7361t.py b/omron/deviceSpecific/hem-7361t.py new file mode 100644 index 0000000..4480e55 --- /dev/null +++ b/omron/deviceSpecific/hem-7361t.py @@ -0,0 +1,56 @@ +import sys +import datetime +import logging +logger = logging.getLogger("omblepy") + +sys.path.append('..') +from sharedDriver import sharedDeviceDriverCode + +class deviceSpecificDriver(sharedDeviceDriverCode): + deviceEndianess = "little" + userStartAdressesList = [0x0098, 0x06D8] + perUserRecordsCountList = [100 , 100 ] + recordByteSize = 0x10 + transmissionBlockSize = 0x10 + + settingsReadAddress = 0x0010 + settingsWriteAddress = 0x0054 + + settingsUnreadRecordsBytes = [0x00, 0x10] + settingsTimeSyncBytes = [0x2C, 0x3C] + + def deviceSpecific_ParseRecordFormat(self, singleRecordAsByteArray): + recordDict = dict() + minute = self._bytearrayBitsToInt(singleRecordAsByteArray, 68, 73) + second = self._bytearrayBitsToInt(singleRecordAsByteArray, 74, 79) + second = min([second, 59]) #for some reason the second value can range up to 63 + recordDict["mov"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 80, 80) + recordDict["ihb"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 81, 81) + month = self._bytearrayBitsToInt(singleRecordAsByteArray, 82, 85) + day = self._bytearrayBitsToInt(singleRecordAsByteArray, 86, 90) + hour = self._bytearrayBitsToInt(singleRecordAsByteArray, 91, 95) + year = self._bytearrayBitsToInt(singleRecordAsByteArray, 98, 103) + 2000 + recordDict["bpm"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 104, 111) + recordDict["dia"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 112, 119) + recordDict["sys"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 120, 127) + 25 + recordDict["datetime"] = datetime.datetime(year, month, day, hour, minute, second) + return recordDict + + def deviceSpecific_syncWithSystemTime(self): + timeSyncSettingsCopy = self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] + #read current time from cached settings bytes + year, month, day, hour, minute, second = [int(byte) for byte in timeSyncSettingsCopy[8:14]] + try: + logger.info(f"device is set to date: {datetime.datetime(year + 2000, month, day, hour, minute, second).strftime('%Y-%m-%d %H:%M:%S')}") + except: + logger.warning(f"device is set to an invalid date") + + #write the current time into the cached settings which will be written later + currentTime = datetime.datetime.now() + setNewTimeDataBytes = timeSyncSettingsCopy[0:8] #Take the first eight bytes from eeprom without modification + setNewTimeDataBytes += bytes([currentTime.year - 2000, currentTime.month, currentTime.day, currentTime.hour, currentTime.minute, currentTime.second]) + setNewTimeDataBytes += bytes([sum(setNewTimeDataBytes) & 0xff, 0x00]) #first byte does not seem to matter, second byte is crc generated by sum over data and only using lower 8 bits + self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] = setNewTimeDataBytes + + logger.info(f"settings updated to new date {currentTime.strftime('%Y-%m-%d %H:%M:%S')}") + return \ No newline at end of file diff --git a/omron/deviceSpecific/hem-7530t.py b/omron/deviceSpecific/hem-7530t.py new file mode 100644 index 0000000..c10c5e2 --- /dev/null +++ b/omron/deviceSpecific/hem-7530t.py @@ -0,0 +1,40 @@ +import sys +import datetime +import logging +logger = logging.getLogger("omblepy") + +sys.path.append('..') +from sharedDriver import sharedDeviceDriverCode + +class deviceSpecificDriver(sharedDeviceDriverCode): + deviceEndianess = "big" + userStartAdressesList = [0x2e8] + perUserRecordsCountList = [90] + recordByteSize = 0x0e + transmissionBlockSize = 0x10 + + settingsReadAddress = 0x0260 + settingsWriteAddress = 0x02a4 + + #settingsUnreadRecordsBytes = [0x00, 0x08] + #settingsTimeSyncBytes = [0x14, 0x1e] + + def deviceSpecific_ParseRecordFormat(self, singleRecordAsByteArray): + recordDict = dict() + recordDict["dia"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 0, 7) + recordDict["sys"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 8, 15) + 25 + year = self._bytearrayBitsToInt(singleRecordAsByteArray, 18, 23) + 2000 + recordDict["bpm"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 24, 31) + recordDict["mov"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 32, 32) + recordDict["ihb"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 33, 33) + month = self._bytearrayBitsToInt(singleRecordAsByteArray, 34, 37) + day = self._bytearrayBitsToInt(singleRecordAsByteArray, 38, 42) + hour = self._bytearrayBitsToInt(singleRecordAsByteArray, 43, 47) + minute = self._bytearrayBitsToInt(singleRecordAsByteArray, 52, 57) + second = self._bytearrayBitsToInt(singleRecordAsByteArray, 58, 63) + second = min([second, 59]) #for some reason the second value can range up to 63 + recordDict["datetime"] = datetime.datetime(year, month, day, hour, minute, second) + return recordDict + + def deviceSpecific_syncWithSystemTime(self): + raise ValueError("not supported") \ No newline at end of file diff --git a/omron/deviceSpecific/hem-7600t.py b/omron/deviceSpecific/hem-7600t.py new file mode 100644 index 0000000..9c8d044 --- /dev/null +++ b/omron/deviceSpecific/hem-7600t.py @@ -0,0 +1,57 @@ +import sys +import datetime +import logging +logger = logging.getLogger("omblepy") + +sys.path.append('..') +from sharedDriver import sharedDeviceDriverCode + +class deviceSpecificDriver(sharedDeviceDriverCode): + deviceEndianess = "big" + userStartAdressesList = [0x02ac] + perUserRecordsCountList = [100 ] + recordByteSize = 0x0e + transmissionBlockSize = 0x38 + + settingsReadAddress = 0x0260 + settingsWriteAddress = 0x0286 + + settingsUnreadRecordsBytes = [0x00, 0x08] + settingsTimeSyncBytes = [0x14, 0x1e] + + def deviceSpecific_ParseRecordFormat(self, singleRecordAsByteArray): + recordDict = dict() + recordDict["dia"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 0, 7) + recordDict["sys"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 8, 15) + 25 + year = self._bytearrayBitsToInt(singleRecordAsByteArray, 16, 23) + 2000 + recordDict["bpm"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 24, 31) + recordDict["mov"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 32, 32) + recordDict["ihb"] = self._bytearrayBitsToInt(singleRecordAsByteArray, 33, 33) + month = self._bytearrayBitsToInt(singleRecordAsByteArray, 34, 37) + day = self._bytearrayBitsToInt(singleRecordAsByteArray, 38, 42) + hour = self._bytearrayBitsToInt(singleRecordAsByteArray, 43, 47) + minute = self._bytearrayBitsToInt(singleRecordAsByteArray, 52, 57) + second = self._bytearrayBitsToInt(singleRecordAsByteArray, 58, 63) + second = min([second, 59]) #for some reason the second value can range up to 63 + recordDict["datetime"] = datetime.datetime(year, month, day, hour, minute, second) + return recordDict + + def deviceSpecific_syncWithSystemTime(self): + + timeSyncSettingsCopy = self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] + #read current time from cached settings bytes + month, year, hour, day, second, minute = [int(byte) for byte in timeSyncSettingsCopy[2:8]] + try: + logger.info(f"device is set to date: {datetime.datetime(year + 2000, month, day, hour, minute, second).strftime('%Y-%m-%d %H:%M:%S')}") + except: + logger.warning(f"device is set to an invalid date") + + #write the current time into the cached settings which will be written later + currentTime = datetime.datetime.now() + setNewTimeDataBytes = timeSyncSettingsCopy[0:2] + setNewTimeDataBytes += bytes([currentTime.month, currentTime.year - 2000, currentTime.hour, currentTime.day, currentTime.second, currentTime.minute]) + setNewTimeDataBytes += bytes([0x00, sum(setNewTimeDataBytes) & 0xff]) #first byte does not seem to matter, second byte is crc generated by sum over data and only using lower 8 bits + self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] = setNewTimeDataBytes + + logger.info(f"settings updated to new date {currentTime.strftime('%Y-%m-%d %H:%M:%S')}") + return \ No newline at end of file diff --git a/omron/omblepy.py b/omron/omblepy.py new file mode 100644 index 0000000..792430a --- /dev/null +++ b/omron/omblepy.py @@ -0,0 +1,397 @@ +#!/usr/bin/python3 + +# Version info +# ================================================ +# Export 2 Garmin Connect v2.2 (omblepy.py) +# ================================================ + +import asyncio #avoid wait on bluetooth stack stalling the application +import terminaltables #for pretty selection table for ble devices +import bleak #bluetooth low energy package for python +import re #regex to match bt mac address +import argparse #to process command line arguments +import datetime +import time +import sys +import pathlib +import logging +import csv +import os + +#global constants +parentService_UUID = "ecbe3980-c9a2-11e1-b1bd-0002a5d5c51b" + +#global variables +bleClient = None +examplePairingKey = bytearray.fromhex("deadbeaf12341234deadbeaf12341234") #arbitrary choise +deviceSpecific = None #imported module for each device +logger = logging.getLogger("omblepy") + +# Code change for Export2Garmin +path = os.path.dirname(os.path.dirname(__file__)) + +def convertByteArrayToHexString(array): + return (bytes(array).hex()) + +class bluetoothTxRxHandler: + #BTLE Characteristic IDs + deviceRxChannelUUIDs = [ + "49123040-aee8-11e1-a74d-0002a5d5c51b", + "4d0bf320-aee8-11e1-a0d9-0002a5d5c51b", + "5128ce60-aee8-11e1-b84b-0002a5d5c51b", + "560f1420-aee8-11e1-8184-0002a5d5c51b" + ] + deviceTxChannelUUIDs = [ + "db5b55e0-aee7-11e1-965e-0002a5d5c51b", + "e0b8a060-aee7-11e1-92f4-0002a5d5c51b", + "0ae12b00-aee8-11e1-a192-0002a5d5c51b", + "10e1ba60-aee8-11e1-89e5-0002a5d5c51b" + ] + deviceDataRxChannelIntHandles = [0x360, 0x370, 0x380, 0x390] + deviceUnlock_UUID = "b305b680-aee7-11e1-a730-0002a5d5c51b" + + def __init__(self, pairing = False): + self.currentRxNotifyStateFlag = False + self.rxPacketType = None + self.rxEepromAddress = None + self.rxDataBytes = None + self.rxFinishedFlag = False + self.rxRawChannelBuffer = [None] * 4 #a buffer for each channel + + async def _enableRxChannelNotifyAndCallback(self): + if(self.currentRxNotifyStateFlag != True): + for rxChannelUUID in self.deviceRxChannelUUIDs: + await bleClient.start_notify(rxChannelUUID, self._callbackForRxChannels) + self.currentRxNotifyStateFlag = True + + async def _disableRxChannelNotifyAndCallback(self): + if(self.currentRxNotifyStateFlag != False): + for rxChannelUUID in self.deviceRxChannelUUIDs: + await bleClient.stop_notify(rxChannelUUID) + self.currentRxNotifyStateFlag = False + + def _callbackForRxChannels(self, BleakGATTChar, rxBytes): + if type(BleakGATTChar) is int: + rxChannelId = self.deviceDataRxChannelIntHandles.index(BleakGATTChar) + else: + rxChannelId = self.deviceDataRxChannelIntHandles.index(BleakGATTChar.handle) + self.rxRawChannelBuffer[rxChannelId] = rxBytes + + logger.debug(f"rx ch{rxChannelId} < {convertByteArrayToHexString(rxBytes)}") + if self.rxRawChannelBuffer[0]: #if there is data present in the first rx buffer + packetSize = self.rxRawChannelBuffer[0][0] + requiredChannels = range((packetSize + 15) // 16) + #are all required channels already recieved + for channelIdx in requiredChannels: + if self.rxRawChannelBuffer[channelIdx] is None: #if one of the required channels is empty wait for more packets to arrive + return + + #check crc + combinedRawRx = bytearray() + for channelIdx in requiredChannels: + combinedRawRx += self.rxRawChannelBuffer[channelIdx] + combinedRawRx = combinedRawRx[:packetSize] #cut extra bytes from the end + xorCrc = 0 + for byte in combinedRawRx: + xorCrc ^= byte + if(xorCrc): + raise ValueError(f"data corruption in rx\ncrc: {xorCrc}\ncombniedBuffer: {convertByteArrayToHexString(combinedRawRx)}") + return + #extract information + self.rxPacketType = combinedRawRx[1:3] + self.rxEepromAddress = combinedRawRx[3:5] + expectedNumDataBytes = combinedRawRx[5] + if(expectedNumDataBytes > (len(combinedRawRx) - 8)): + self.rxDataBytes = bytes(b'\xff') * expectedNumDataBytes + else: + if(self.rxPacketType) == bytearray.fromhex("8f00"): #need special case for end of transmission packet, otherwise transmission error code is not accessible + self.rxDataBytes = combinedRawRx[6:7] + else: + self.rxDataBytes = combinedRawRx[6: 6 + expectedNumDataBytes] + self.rxRawChannelBuffer = [None] * 4 #clear channel buffers + self.rxFinishedFlag = True + return + return + + async def _waitForRxOrRetry(self, command, timeoutS = 1.0): + self.rxFinishedFlag = False + retries = 0 + while True: + commandCopy = command + requiredTxChannels = range((len(command) + 15) // 16) + for channelIdx in requiredTxChannels: + logger.debug(f"tx ch{channelIdx} > {convertByteArrayToHexString(commandCopy[:16])}") + await bleClient.write_gatt_char(self.deviceTxChannelUUIDs[channelIdx], commandCopy[:16]) + commandCopy = commandCopy[16:] + + currentTimeout = timeoutS + while(self.rxFinishedFlag == False): + await asyncio.sleep(0.1) + currentTimeout -= 0.1 + if(currentTimeout < 0): + break + if(currentTimeout >= 0): + break + retries += 1 + logger.warning(f"Transmission failed, count of retries: {retries} / 5") + if(retries >= 5): + ValueError("Same transmission failed 5 times, abort") + return + + async def startTransmission(self): + await self._enableRxChannelNotifyAndCallback() + startDataReadout = bytearray.fromhex("0800000000100018") + await self._waitForRxOrRetry(startDataReadout) + if(self.rxPacketType != bytearray.fromhex("8000")): + raise ValueError("invalid response to data readout start") + + async def endTransmission(self): + stopDataReadout = bytearray.fromhex("080f000000000007") + await self._waitForRxOrRetry(stopDataReadout) + if(self.rxPacketType != bytearray.fromhex("8f00")): + raise ValueError("invlid response to data readout end") + return + if(self.rxDataBytes[0]): + raise ValueError(f"Device reported error status code {self.rxDataBytes[0]} while sending endTransmission command.") + return + await self._disableRxChannelNotifyAndCallback() + + async def _writeBlockEeprom(self, address, dataByteArray): + dataWriteCommand = bytearray() + dataWriteCommand += (len(dataByteArray) + 8).to_bytes(1, 'big') #total packet size with 6byte header and 2byte crc + dataWriteCommand += bytearray.fromhex("01c0") + dataWriteCommand += address.to_bytes(2, 'big') + dataWriteCommand += len(dataByteArray).to_bytes(1, 'big') + dataWriteCommand += dataByteArray + #calculate and append crc + xorCrc = 0 + for byte in dataWriteCommand: + xorCrc ^= byte + dataWriteCommand += b'\x00' + dataWriteCommand.append(xorCrc) + await self._waitForRxOrRetry(dataWriteCommand) + if(self.rxEepromAddress != address.to_bytes(2, 'big')): + raise ValueError(f"recieved packet address {self.rxEepromAddress} does not match the written address {address.to_bytes(2, 'big')}") + if(self.rxPacketType != bytearray.fromhex("81c0")): + raise ValueError("Invalid packet type in eeprom write") + return + + async def _readBlockEeprom(self, address, blocksize): + dataReadCommand = bytearray.fromhex("080100") + dataReadCommand += address.to_bytes(2, 'big') + dataReadCommand += blocksize.to_bytes(1, 'big') + #calculate and append crc + xorCrc = 0 + for byte in dataReadCommand: + xorCrc ^= byte + dataReadCommand += b'\x00' + dataReadCommand.append(xorCrc) + await self._waitForRxOrRetry(dataReadCommand) + if(self.rxEepromAddress != address.to_bytes(2, 'big')): + raise ValueError(f"revieved packet address {self.rxEepromAddress} does not match requested address {address.to_bytes(2, 'big')}") + if(self.rxPacketType != bytearray.fromhex("8100")): + raise ValueError("Invalid packet type in eeprom read") + return self.rxDataBytes + + async def writeContinuousEepromData(self, startAddress, bytesArrayToWrite, btBlockSize = 0x08): + while(len(bytesArrayToWrite) != 0): + nextSubblockSize = min(len(bytesArrayToWrite), btBlockSize) + logger.debug(f"write to {hex(startAddress)} size {hex(nextSubblockSize)}") + await self._writeBlockEeprom(startAddress, bytesArrayToWrite[:nextSubblockSize]) + bytesArrayToWrite = bytesArrayToWrite[nextSubblockSize:] + startAddress += nextSubblockSize + return + + async def readContinuousEepromData(self, startAddress, bytesToRead, btBlockSize = 0x10): + eepromBytesData = bytearray() + while(bytesToRead != 0): + nextSubblockSize = min(bytesToRead, btBlockSize) + logger.debug(f"read from {hex(startAddress)} size {hex(nextSubblockSize)}") + eepromBytesData += await self._readBlockEeprom(startAddress, nextSubblockSize) + startAddress += nextSubblockSize + bytesToRead -= nextSubblockSize + return eepromBytesData + + def _callbackForUnlockChannel(self, UUID_or_intHandle, rxBytes): + self.rxDataBytes = rxBytes + self.rxFinishedFlag = True + return + + async def writeNewUnlockKey(self, newKeyByteArray = examplePairingKey): + if(len(newKeyByteArray) != 16): + raise ValueError(f"key has to be 16 bytes long, is {len(newKeyByteArray)}") + return + #enable key programming mode + await bleClient.start_notify(self.deviceUnlock_UUID, self._callbackForUnlockChannel) + self.rxFinishedFlag = False + await bleClient.write_gatt_char(self.deviceUnlock_UUID, b'\x02' + b'\x00'*16, response=True) + while(self.rxFinishedFlag == False): + await asyncio.sleep(0.1) + deviceResponse = self.rxDataBytes + if(deviceResponse[:2] != bytearray.fromhex("8200")): + raise ValueError(f"Could not enter key programming mode. Has the device been started in pairing mode? Got response: {deviceResponse}") + return + #program new key + self.rxFinishedFlag = False + await bleClient.write_gatt_char(self.deviceUnlock_UUID, b'\x00' + newKeyByteArray, response=True) + while(self.rxFinishedFlag == False): + await asyncio.sleep(0.1) + deviceResponse = self.rxDataBytes + if(deviceResponse[:2] != bytearray.fromhex("8000")): + raise ValueError(f"Failure to program new key. Response: {deviceResponse}") + return + await bleClient.stop_notify(self.deviceUnlock_UUID) + logger.info(f"Paired device successfully with new key {newKeyByteArray}.") + logger.info("From now on you can connect omit the -p flag, even on other PCs with different bluetooth-mac-addresses.") + return + + async def unlockWithUnlockKey(self, keyByteArray = examplePairingKey): + await bleClient.start_notify(self.deviceUnlock_UUID, self._callbackForUnlockChannel) + self.rxFinishedFlag = False + await bleClient.write_gatt_char(self.deviceUnlock_UUID, b'\x01' + keyByteArray, response=True) + while(self.rxFinishedFlag == False): + await asyncio.sleep(0.1) + deviceResponse = self.rxDataBytes + if(deviceResponse[:2] != bytearray.fromhex("8100")): + raise ValueError(f"entered pairing key does not match stored one.") + return + await bleClient.stop_notify(self.deviceUnlock_UUID) + return + +# Code change for Export2Garmin +def readCsv(filename): + records = [] + with open(filename, mode='r', newline='', encoding='utf-8') as infile: + reader = csv.DictReader(infile, delimiter=';') + for oldRecordDict in reader: + oldRecordDict["datetime"] = datetime.datetime.strptime(oldRecordDict["datetime"], "%d.%m.%Y %H:%M") + records.append(oldRecordDict) + return records +def appendCsv(allRecords): + for userIdx in range(len(allRecords)): + oldCsvFile = pathlib.Path(f"/dev/shm/omron_user{userIdx+1}.csv") + datesOfNewRecords = [record["datetime"] for record in allRecords[userIdx]] + if(oldCsvFile.is_file()): + records = readCsv(f"/dev/shm/omron_user{userIdx+1}.csv") + allRecords[userIdx].extend(filter(lambda x: x["datetime"] not in datesOfNewRecords,records)) + allRecords[userIdx] = sorted(allRecords[userIdx], key = lambda x: x["datetime"]) + logger.info(f"writing data to omron_user{userIdx+1}.csv") + with open(f"/dev/shm/omron_user{userIdx+1}.csv", mode='w', newline='', encoding='utf-8') as outfile: + writer = csv.DictWriter(outfile, delimiter=';', fieldnames = ["Data Status", "Unix Time", "datetime", "sys", "dia", "bpm", "mov", "ihb", "User"]) + writer.writeheader() + for recordDict in allRecords[userIdx]: + recordDict["Data Status"] = "to_import" + recordDict["Unix Time"] = int(time.mktime(recordDict["datetime"].timetuple())) + recordDict["datetime"] = recordDict["datetime"].strftime("%d.%m.%Y %H:%M") + recordDict["User"] = (f"user{userIdx+1}") + writer.writerow(recordDict) + +# Code change for Export2Garmin +async def selectBLEdevices(adapter): + print("Select your Omron device from the list below...") + while(True): + devices = await bleak.BleakScanner.discover(adapter=adapter, return_adv=True) + devices = list(sorted(devices.items(), key = lambda x: x[1][1].rssi, reverse=True)) + tableEntries = [] + tableEntries.append(["ID", "MAC", "NAME", "RSSI"]) + for deviceIdx, (macAddr, (bleDev, advData)) in enumerate(devices): + tableEntries.append([deviceIdx, macAddr, bleDev.name, advData.rssi]) + print(terminaltables.AsciiTable(tableEntries).table) + res = input("Enter ID or just press Enter to rescan.\n") + if(res.isdigit() and int(res) in range(len(devices))): + break + return devices[int(res)][0] + +async def main(): + global bleClient + global deviceSpecific + parser = argparse.ArgumentParser(description="python tool to read the records of omron blood pressure instruments") + parser.add_argument('-d', "--device", required="true", type=ascii, help="Device name (e.g. HEM-7322T-D).") + parser.add_argument("--loggerDebug", action="store_true", help="Enable verbose logger output") + parser.add_argument("-p", "--pair", action="store_true", help="Programm the pairing key into the device. Needs to be done only once.") + parser.add_argument("-m", "--mac", type=ascii, help="Bluetooth Mac address of the device (e.g. 00:1b:63:84:45:e6). If not specified, will scan for devices and display a selection dialog.") + parser.add_argument('-n', "--newRecOnly", action="store_true", help="Considers the unread records counter and only reads new records. Resets these counters afterwards. If not enabled, all records are read and the unread counters are not cleared.") + parser.add_argument('-t', "--timeSync", action="store_true", help="Update the time on the omron device by using the current system time.") + + # Code change for Export2Garmin + parser.add_argument('-a', "--adapter", required="true", type=str, help="Choose which HCI adapter you want to scan with (e.g. hci0).") + args = parser.parse_args() + + #setup logging + handler = logging.StreamHandler() + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + if(args.loggerDebug): + logger.setLevel(logging.DEBUG) + else: + logger.setLevel(logging.INFO) + + #import device specific module + if(not args.pair and not args.device): + raise ValueError("When not in pairing mode, please specify your device type name with -d or --device") + return + if(args.device): + deviceName = args.device.strip("'").strip('\"') #strip quotes around arg + + # Code change for Export2Garmin + sys.path.insert(0, path + "/omron/deviceSpecific") + try: + logger.info(f"Attempt to import module for device {deviceName.lower()}") + deviceSpecific = __import__(deviceName.lower()) + except ImportError: + raise ValueError("the device is no supported yet, you can help by contributing :)") + return + + #select device mac address + validMacRegex = re.compile(r"^([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2})$") + if(args.mac is not None): + btmac = args.mac.strip("'").strip('\"') #strip quotes around arg + if(validMacRegex.match(btmac) is None): + raise ValueError(f"argument after -m or --mac {btmac} is not a valid mac address") + return + bleAddr = btmac + else: + print("To improve your chance of a successful connection please do the following:") + print(" -remove previous device pairings in your OS's bluetooth dialog") + print(" -enable bluetooth on you omron device and use the specified mode (pairing or normal)") + print(" -do not accept any pairing dialog until you selected your device in the following list\n") + + # Code change for Export2Garmin + bleAddr = await selectBLEdevices(adapter=args.adapter) + bleClient = bleak.BleakClient(bleAddr, adapter=args.adapter) + try: + logger.info(f"Attempt connecting to {bleAddr}.") + await bleClient.connect() + await asyncio.sleep(0.5) + await bleClient.pair(protection_level = 2) + #verify that the device is an omron device by checking presence of certain bluetooth services + if parentService_UUID not in [service.uuid for service in bleClient.services]: + raise OSError("""Some required bluetooth attributes not found on this ble device. + This means that either, you connected to a wrong device, + or that your OS has a bug when reading BT LE device attributes (certain linux versions).""") + return + bluetoothTxRxObj = bluetoothTxRxHandler() + if(args.pair): + await bluetoothTxRxObj.writeNewUnlockKey() + #this seems to be necessary when the device has not been paired to any device + await bluetoothTxRxObj.startTransmission() + await bluetoothTxRxObj.endTransmission() + else: + logger.info("communication started") + devSpecificDriver = deviceSpecific.deviceSpecificDriver() + allRecs = await devSpecificDriver.getRecords(btobj = bluetoothTxRxObj, useUnreadCounter = args.newRecOnly, syncTime = args.timeSync) + logger.info("communication finished") + appendCsv(allRecs) + finally: + logger.info("unpair and disconnect") + if bleClient.is_connected: + await bleClient.unpair() + try: + await bleClient.disconnect() + except AssertionError as e: + logger.error("Bleak AssertionError during disconnect. This usually happens when using the bluezdbus adapter.") + logger.error("You can find the upstream issue at: https://github.com/hbldh/bleak/issues/641") + logger.error(f"AssertionError details: {e}") + +asyncio.run(main()) \ No newline at end of file diff --git a/omron/omron_export.py b/omron/omron_export.py new file mode 100644 index 0000000..8ab9dac --- /dev/null +++ b/omron/omron_export.py @@ -0,0 +1,74 @@ +#!/usr/bin/python3 + +import os +import csv +from datetime import datetime as dt +from garminconnect import Garmin + +# Version info +print(""" +============================================= +Export 2 Garmin Connect v2.0 (omron_export.py +============================================= +""") + +# Importing user variables from a file +path = os.path.dirname(os.path.dirname(__file__)) +with open(path + '/user/export2garmin.cfg', 'r') as file: + for line in file: + line = line.strip() + if line.startswith('omron_export_category'): + name, value = line.split('=') + globals()[name.strip()] = value.strip() + +# Import data variables from a file +with open(path + '/user/omron_backup.csv', 'r') as csv_file: + csv_reader = csv.reader(csv_file, delimiter=';') + for row in csv_reader: + if str(row[0]) in ["failed", "to_import"]: + unixtime = int(row[1]) + omrondate = str(row[2]) + omrontime = str(row[3]) + systolic = int(row[4]) + diastolic = int(row[5]) + pulse = int(row[6]) + MOV = int(row[7]) + IHB = int(row[8]) + emailuser = str(row[9]) + + # Determine blood pressure category + omron_export_category = str(omron_export_category) + category = "None" + if omron_export_category == 'eu': + if systolic < 130 and diastolic < 85: + category = "Normal" + elif (130 <= systolic <= 139 and diastolic < 85) or (systolic < 130 and 85 <= diastolic <= 89): + category = "High-Normal" + elif (140 <= systolic <= 159 and diastolic < 90) or (systolic < 140 and 90 <= diastolic <= 99): + category = "Grade_1" + elif (160 <= systolic <= 179 and diastolic < 100) or (systolic < 160 and 100 <= diastolic <= 109): + category = "Grade_2" + elif omron_export_category == 'us': + if systolic < 120 and diastolic < 80: + category = "Normal" + elif (120 <= systolic <= 129) and diastolic < 80: + category = "High-Normal" + elif (130 <= systolic <= 139) or (80 <= diastolic <= 89): + category = "Grade_1" + elif (systolic >= 140) or (diastolic >= 90): + category = "Grade_2" + + # Print to temp.log file + print(f"OMRON * Import data: {unixtime};{omrondate};{omrontime};{systolic:.0f};{diastolic:.0f};{pulse:.0f};{MOV:.0f};{IHB:.0f};{emailuser}") + print(f"OMRON * Calculated data: {category};{MOV:.0f};{IHB:.0f};{emailuser};{dt.now().strftime('%d.%m.%Y;%H:%M')}") + + # Login to Garmin Connect + with open(path + '/user/' + emailuser, 'r') as token_file: + tokenstore = token_file.read() + garmin = Garmin() + garmin.login(tokenstore) + + # Upload data to Garmin Connect + garmin.set_blood_pressure(timestamp=dt.fromtimestamp(unixtime).isoformat(),diastolic=diastolic,systolic=systolic,pulse=pulse) + print("OMRON * Upload status: OK") + break \ No newline at end of file diff --git a/omron/omron_pairing.sh b/omron/omron_pairing.sh new file mode 100644 index 0000000..6bf162b --- /dev/null +++ b/omron/omron_pairing.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Version info +echo -e "\n===============================================" +echo -e "Export 2 Garmin Connect v2.2 (omron_pairing.sh)" +echo -e "===============================================\n" + +timenow() { + date +%d.%m.%Y-%H:%M:%S +} +path=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." &> /dev/null && pwd) + +# Verifying correct working of BLE, restart bluetooth service and device via miscale_ble.py +echo "$(timenow) SYSTEM * BLE adapter check if available" +ble_check=$(python3 -B $path/miscale/miscale_ble.py) +if echo $ble_check | grep -q "failed" ; then + echo "$(timenow) SYSTEM * BLE adapter not working, skip pairing" +else ble_status=ok + hci_mac=$(echo $ble_check | grep -o 'h.\{21\})' | head -n 1) + echo "$(timenow) SYSTEM * BLE adapter $hci_mac working, go to pairing" +fi + +# Workaround for pairing +if [[ $ble_status == "ok" ]] ; then + source <(grep omron_omblepy_ $path/user/export2garmin.cfg) + omron_hci=$(echo $ble_check | grep -o 'hci.' | head -n 1) + coproc bluetoothctl + if [ $omron_omblepy_debug == "on" ] ; then + python3 -B $path/omron/omblepy.py -a $omron_hci -p -d $omron_omblepy_model --loggerDebug + else + python3 -B $path/omron/omblepy.py -a $omron_hci -p -d $omron_omblepy_model + fi +fi \ No newline at end of file diff --git a/omron/sharedDriver.py b/omron/sharedDriver.py new file mode 100644 index 0000000..c911d0f --- /dev/null +++ b/omron/sharedDriver.py @@ -0,0 +1,138 @@ +import logging +logger = logging.getLogger("omblepy") + +class sharedDeviceDriverCode(): + #these need to be overwritten by device specific version + deviceEndianess = None + userStartAdressesList = None + perUserRecordsCountList = None + recordByteSize = None + transmissionBlockSize = None + settingsReadAddress = None + settingsWriteAddress = None + settingsUnreadRecordsBytes = None + settingsTimeSyncBytes = None + + #abstract method, implemented by the device specific driver + def deviceSpecific_ParseRecordFormat(self, singleRecordAsByteArray): + raise NotImplementedError("Please Implement this method in the device specific file.") + + #abstract method, implemented by the device specific driver + def deviceSpecific_syncWithSystemTime(self): + raise NotImplementedError("Please Implement this method in the device specific file.") + + def _bytearrayBitsToInt(self, bytesArray, firstValidBitIdx, lastvalidBitIdx): + bigInt = int.from_bytes(bytesArray, self.deviceEndianess) + numValidBits = (lastvalidBitIdx-firstValidBitIdx) + 1 + shiftedBits = (bigInt>>(len(bytesArray) * 8 - (lastvalidBitIdx + 1))) + bitmask = (2**(numValidBits)-1) + return shiftedBits & bitmask + + def resetUnreadRecordsCounter(self): + #special code for no new records is 0x8000 + unreadRecordsSettingsCopy = self.cachedSettingsBytes[slice(*self.settingsUnreadRecordsBytes)] + resetUnreadRecordsBytes = (0x8000).to_bytes(2, byteorder=self.deviceEndianess) + newUnreadRecordSettings = unreadRecordsSettingsCopy[:4] + resetUnreadRecordsBytes * 2 + unreadRecordsSettingsCopy[8:] + self.cachedSettingsBytes[slice(*self.settingsUnreadRecordsBytes)] = newUnreadRecordSettings + + async def getRecords(self, btobj, useUnreadCounter, syncTime): + await btobj.unlockWithUnlockKey() + await btobj.startTransmission() + + #cache settings for time sync and for unread record counter + + if(syncTime or useUnreadCounter): + #initialize cached settings bytes with zeros and use bytearray so that the values are mutable + self.cachedSettingsBytes = bytearray(b'\0' * (self.settingsWriteAddress - self.settingsReadAddress)) + for section in [self.settingsUnreadRecordsBytes, self.settingsTimeSyncBytes]: + sectionNumBytes = section[1] - section[0] + if(sectionNumBytes >= 54): + raise ValueError("Section to big for a single read") + self.cachedSettingsBytes[slice(*section)] = await btobj.readContinuousEepromData(self.settingsReadAddress+section[0], sectionNumBytes, sectionNumBytes) + + if(useUnreadCounter): + allUsersReadCommandsList = await self._getReadCommands_OnlyNewRecords() + else: + allUsersReadCommandsList = await self._getReadCommands_AllRecords() + + #read records for all users + logger.info("start reading data, this can take a while, use debug flag to see progress") + allUserRecordsList = [] + for userIdx, userReadCommandsList in enumerate(allUsersReadCommandsList): + userConcatenatedRecordBytes = bytearray() + for readCommand in userReadCommandsList: + userConcatenatedRecordBytes += await btobj.readContinuousEepromData(readCommand["address"], readCommand["size"], self.transmissionBlockSize) + #seperate the concatenated bytes into individual records + perUserAnalyzedRecordsList = [] + for recordStartOffset in range(0, len(userConcatenatedRecordBytes), self.recordByteSize): + singleRecordBytes = userConcatenatedRecordBytes[recordStartOffset:recordStartOffset+self.recordByteSize] + if singleRecordBytes != b'\xff' * self.recordByteSize: + try: + singleRecordDict = self.deviceSpecific_ParseRecordFormat(singleRecordBytes) + perUserAnalyzedRecordsList.append(singleRecordDict) + except: + logger.warning(f"Error parsing record for user{userIdx+1} at offset {recordStartOffset} data {bytes(singleRecordBytes).hex()}, ignoring this record.") + allUserRecordsList.append(perUserAnalyzedRecordsList) + + if(useUnreadCounter): + self.resetUnreadRecordsCounter() + + #maybe this could be combined into a single write + if(syncTime): + self.deviceSpecific_syncWithSystemTime() + bytesToWrite = self.cachedSettingsBytes[slice(*self.settingsTimeSyncBytes)] + await btobj.writeContinuousEepromData(self.settingsWriteAddress + self.settingsTimeSyncBytes[0], bytesToWrite, btBlockSize = len(bytesToWrite)) + if(useUnreadCounter): + bytesToWrite = self.cachedSettingsBytes[slice(*self.settingsUnreadRecordsBytes)] + await btobj.writeContinuousEepromData(self.settingsWriteAddress + self.settingsUnreadRecordsBytes[0], bytesToWrite, btBlockSize = len(bytesToWrite)) + + await btobj.endTransmission() + return allUserRecordsList + + def calcRingBufferRecordReadLocations(self, userIdx, unreadRecords, lastWrittenSlot): + userReadCommandsList = [] + if(lastWrittenSlot < unreadRecords): #two reads neccesary, because ring buffer start reached + #read start of ring buffer + firstRead = dict() + firstRead["address"] = self.userStartAdressesList[userIdx] + firstRead["size"] = self.recordByteSize * lastWrittenSlot + userReadCommandsList.append(firstRead) + + #read end of ring buffer + secondRead = dict() + secondRead["address"] = self.userStartAdressesList[userIdx] + secondRead["address"] += (self.perUserRecordsCountList[userIdx] + lastWrittenSlot - unreadRecords) * self.recordByteSize + secondRead["size"] = self.recordByteSize * (unreadRecords - lastWrittenSlot) + userReadCommandsList.append(secondRead) + else: + #read start of ring buffer + firstRead = dict() + firstRead["address"] = self.userStartAdressesList[userIdx] + firstRead["address"] += self.recordByteSize * (lastWrittenSlot - unreadRecords) + firstRead["size"] = self.recordByteSize * unreadRecords + userReadCommandsList.append(firstRead) + return userReadCommandsList + + async def _getReadCommands_OnlyNewRecords(self): + allUsersReadCommandsList = [] + readRecordsInfoByteArray = self.cachedSettingsBytes[slice(*self.settingsUnreadRecordsBytes)] + numUsers = len(self.userStartAdressesList) + for userIdx in range(numUsers): + #byte location depends on endianess, so use _bytearrayBitsToInt to account for this + lastWrittenSlotForUser = self._bytearrayBitsToInt(readRecordsInfoByteArray[2*userIdx+0:2*userIdx+2], 8, 15) + unreadRecordsForUser = self._bytearrayBitsToInt(readRecordsInfoByteArray[2*userIdx+4:2*userIdx+6], 8, 15) + + logger.info(f"Current ring buffer slot user{userIdx+1}: {lastWrittenSlotForUser}.") + logger.info(f"Unread records user{userIdx+1}: {unreadRecordsForUser}.") + readCmds = self.calcRingBufferRecordReadLocations(userIdx, unreadRecordsForUser, lastWrittenSlotForUser) + allUsersReadCommandsList.append(readCmds) + return allUsersReadCommandsList + async def _getReadCommands_AllRecords(self): + allUsersReadCommandsList = [] + for userIdx, userStartAddress in enumerate(self.userStartAdressesList): + readCommand = dict() + readCommand["address"] = userStartAddress + readCommand["size"] = self.perUserRecordsCountList[userIdx] * self.recordByteSize + singleUserReadCommands = [readCommand] + allUsersReadCommandsList.append(singleUserReadCommands) + return allUsersReadCommandsList diff --git a/user/export2garmin.cfg b/user/export2garmin.cfg new file mode 100644 index 0000000..1a1f346 --- /dev/null +++ b/user/export2garmin.cfg @@ -0,0 +1,81 @@ +# Version info +# ================================================ +# Export 2 Garmin Connect v2.2 (export2garmin.cfg) +# ================================================ + +# HCI number assigned to BLE adapter, default is 0 (hci0) +ble_adapter_hci=0 + +# Enabling BLE adapter search by MAC address instead of HCI number. Allowed switch parameter is "off" or "on" +ble_adapter_switch=off + +# If you set the above parameter to "on", enter BLE adapter MAC adress, please use uppercase letters +ble_adapter_mac=00:00:00:00:00:00 + +# BLE adpater scan time in seconds, default is 10 +ble_adapter_time=10 + +# Enabling checking whether any BLE devices have been detected. Allowed switch parameter is "off" or "on" +ble_adapter_check=off + +# If you set the above parameter to "on", set number of attempts, default is 3 +ble_adapter_repeat=3 + + +# ================================================ +# Mi Body Composition Scale 2 options: +# ================================================ + +# Enabling Mi scale synchronization. Allowed switch parameter is "off" or "on" +switch_miscale=off + +# Time offset parameter in seconds, default is 0. Change to e.g. -3600 or 3600 +miscale_time_offset=0 + +# Protection against unsynchronization of scale time. Time shift parameter in seconds, default is 1200 +miscale_time_unsync=1200 + +# Protection against duplicates. Difference between weighting in seconds, default is 30 +miscale_time_check=30 + +# Adding all users in following format (sex, height in cm, birthdate in dd-mm-yyyy, email to Garmin Connect, max_weight in kg, min_weight in kg) +miscale_export_user1=("male", 172, "02-04-1984", "email@email.com", 65, 53) +miscale_export_user2=("male", 188, "02-04-1984", "email@email.com", 92, 85) + +# Parameters for MQTT broker, skip if you are not using. Allowed switch parameter is "off" or "on" +switch_mqtt=off +miscale_mqtt_passwd=password +miscale_mqtt_user=admin + +# If you are using a BLE adapter enter scale MAC adress, please use uppercase letters +ble_miscale_mac=00:00:00:00:00:00 + + +# ================================================ +# Omron Blood Pressure options: +# ================================================ + +# Enabling Omron synchronization. Allowed switch parameter is "off" or "on" +switch_omron=off + +# Enter Omron model. Allowed parameter is "hem-6232t", "hem-7150t", "hem-7155t", "hem-7322t", "hem-7342t", "hem-7361t", "hem-7530t", "hem-7600t" +omron_omblepy_model=hem-7361t + +# Enter Omron MAC adress, please use uppercase letters +omron_omblepy_mac=00:00:00:00:00:00 + +# BLE adpater scan time in seconds, default is 10 +omron_omblepy_time=10 + +# Enabling debug omblepy. Allowed parameter is "off" or "on" +omron_omblepy_debug=off + +# Enabling downloading all records, recommended only one-time import. Allowed parameter is "off" or "on" +omron_omblepy_all=off + +# Adding max 2 users in following format (email to Garmin Connect) +omron_export_user1=email@email.com +omron_export_user2=email@email.com + +# Choose blood pressure category classification by country in omron_backup.csv file. Allowed switch parameter is "eu" or "us" +omron_export_category=eu \ No newline at end of file diff --git a/user/import_tokens.py b/user/import_tokens.py new file mode 100644 index 0000000..8d50c4a --- /dev/null +++ b/user/import_tokens.py @@ -0,0 +1,46 @@ +#!/usr/bin/python3 + +import os +import datetime +import requests +from getpass import getpass +from garth.exc import GarthHTTPError +from garminconnect import Garmin, GarminConnectAuthenticationError + +# Version info +print(""" +=============================================== +Export 2 Garmin Connect v2.0 (import_tokens.py) +=============================================== +""") + +# Get user credentials +def get_credentials(): + email = input(datetime.datetime.now().strftime("%d.%m.%Y-%H:%M:%S") + " * Login e-mail: ") + password = getpass(datetime.datetime.now().strftime("%d.%m.%Y-%H:%M:%S") + " * Enter password: ") + return email, password + +def get_mfa(): + return input(datetime.datetime.now().strftime("%d.%m.%Y-%H:%M:%S") + " * MFA/2FA one-time code: ") + +# Initialize Garmin API with your credentials without/and MFA/2FA +def init_api(): + try: + email, password = get_credentials() + garmin = Garmin(email, password, is_cn=False, prompt_mfa=get_mfa) + garmin.login() + +# Create Oauth1 and Oauth2 tokens as base64 encoded string + tokenstore_base64 = os.path.dirname(os.path.abspath(__file__)) + token_base64 = garmin.garth.dumps() + dir_path = os.path.expanduser(os.path.join(tokenstore_base64, email)) + with open(dir_path, "w") as token_file: + token_file.write(token_base64) + print(datetime.datetime.now().strftime("%d.%m.%Y-%H:%M:%S") + " * Oauth tokens saved correctly") + except (FileNotFoundError, GarthHTTPError, GarminConnectAuthenticationError, requests.exceptions.HTTPError) as err: + print(err) + return None + +# Main program loop +if __name__ == "__main__": + init_api() \ No newline at end of file