Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvement of node interface #49

Closed
wants to merge 5 commits into from
Closed

Improvement of node interface #49

wants to merge 5 commits into from

Conversation

pirower
Copy link
Contributor

@pirower pirower commented Jun 25, 2020

Hi, please let me know if you want me to incorporate all added features from the child Node_easy directly to Node. As I understood "Node" in the folder "easy" is already a facilitation to use message etc. from the base folder. Nether the less, Node doesn't implement any Ant+ devices (e.g heart rate montior) and so on. I added some features to enable unexperienced users to execute some functions (scan, connect to a heart rate monitor, disconnect, etc...) without much effort. In addition I put node.start into a thread to be able to execute commands (connect, disconnect, scan) while the ant+ dongle is running.
If you think it is to confusing and should be added directly into Node, please let me know

In node.py I added the function "get_capabilities" which returns the capabilities of the attached Ant+ dongle. This function is executed during the initialisation of Node. I changed the list of self.channels to a vector of fixed size ( self.channels = [None]*cap["max_channels"] ). Every time a new channel is created, the code looks up if there is still a free channel left. I also added a function to remove a channel (including unassignment and closing of the channel)

@coveralls
Copy link

coveralls commented Jun 25, 2020

Coverage Status

Coverage decreased (-0.7%) to 56.696% when pulling 6265250 on pirower:test into ae9e736 on Tigge:master.

@pirower
Copy link
Contributor Author

pirower commented Jul 20, 2020

Hi Tigge, could you please provide me with some feedback. Thx

Copy link
Owner

@Tigge Tigge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, thank you for your PR. And, yes the easy packages are an imperative way of calling the underlying base layer. In easy we send a command, then wait for an appropriate answer to that command, and then return from the function.

ANT+ support was planned to en up in a separate package, ant.plus.

Main file `ant/plus/pages.py? from that package
# Ant
#
# Copyright (c) 2012, Gustav Tiger <gustav@tiger.name>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

from __future__ import absolute_import, print_function, division

import struct
import datetime


class Page:

    class Type:

        MANUFACTURERS_INFORMATION = 0x50
        PRODUCT_INFORMATION = 0x51
        BATTERY_STATUS = 0x52
        TIME_AND_DATE = 83
        SUBFIELD_DATA = 84
        HEARTH_RATE_0 = 0x00
        HEARTH_RATE_1 = 0x01
        HEARTH_RATE_2 = 0x02
        HEARTH_RATE_3 = 0x03
        HEARTH_RATE_4 = 0x04

    def __init__(self, data):
        self._data = data

    def get_page_number(self):
        return self._data[0]

    def get_data(self):
        return self._data[1:]

    @classmethod
    def parse(cls, data):
        return cls(data)


class CommonPage(Page):

    def get_data(self):
        return self._data[3:]


class ManufacturersInformationPage(CommonPage):

    def get_hw_revision(self):
        return self._data[3]

    def get_manufacturer_id(self):
        return struct.unpack("<H", self._data[4:6])[0]

    def get_model_number(self):
        return struct.unpack("<H", self._data[6:8])[0]


class ProductInformationPage(CommonPage):

    def get_sw_revision(self):
        return (self._data[3], self._data[2])

    def get_serial_number(self):
        return struct.unpack("<I", self._data[4:8])[0]


class BatteryStatusPage(CommonPage):

    class Status:
        NEW = 0x01
        GOOD = 0x02
        OK = 0x03
        LOW = 0x04
        CRITICAL = 0x05

    def get_number_of_batteries(self):
        return (self._data[2] & 0b11110000) >> 4

    def get_identifier(self):
        return self._data[2] & 0b00001111

    def get_operating_time(self):
        value = (self._data[5] << 16) + (self._data[4] << 8) + (self._data[3])
        resolution = (self._data[7] & 0b10000000) >> 7
        return value * 2 if resolution == 1 else value * 16

    def get_voltage(self):
        return (self._data[7] & 0b00001111) + self._data[6] / 0x100

    def get_status(self):
        return (self._data[7] & 0b01110000) >> 4


class TimeAndDatePage(CommonPage):

    def get_datetime(self):
        datetime.datetime(2000 + self._data[7], self._data[6], self._data[5] & 0b00011111,
                          self._data[4], self._data[3], self._data[2])


class SubfieldDataPage(CommonPage):

    def get_subpage1(self):
        return self._data[2]

    def get_subpage1(self):
        return self._data[3]


class HearthRatePage(CommonPage):

    def get_event_time(self):
        return struct.unpack("<xxxxHxx", self._data)[0] / 1024

    def get_count(self):
        return struct.unpack("<xxxxxxBx", self._data)[0]

    def get_computed_heath_rate(self):
        return struct.unpack("<xxxxxxxB", self._data)[0]

_classes = {
    # Commands
    Page.Type.MANUFACTURERS_INFORMATION: ManufacturersInformationPage,
    Page.Type.PRODUCT_INFORMATION: ProductInformationPage,
    Page.Type.BATTERY_STATUS: BatteryStatusPage,
    Page.Type.TIME_AND_DATE: TimeAndDatePage,
    Page.Type.SUBFIELD_DATA: SubfieldDataPage,
    Page.Type.HEARTH_RATE_0: HearthRatePage,
    Page.Type.HEARTH_RATE_1: HearthRatePage,
    Page.Type.HEARTH_RATE_2: HearthRatePage,
    Page.Type.HEARTH_RATE_3: HearthRatePage,
    Page.Type.HEARTH_RATE_4: HearthRatePage}


def parse(data):
    command_class = _classes[data[0] & 0b01111111]
    return command_class.parse(data)

I can upload the rest of what I have lying around if you want to take a look at it. It would be nice to move this out to a separate package. Modifying the heart rate example to use the ant.plus package instead would look something like (and yeah I don't know how i manage to misspell heart rate):

@@ -26,6 +26,8 @@ from ant.easy.node import Node
 from ant.easy.channel import Channel
 from ant.base.message import Message
 
+import ant.plus.pages
+
 import logging
 import struct
 import threading
@@ -35,12 +37,15 @@ NETWORK_KEY= [0xb9, 0xa5, 0x21, 0xfb, 0xbd, 0x72, 0xc3, 0x45]
 
 
 def on_data(data):
-    hearthrate = data[7]
-    string = "Hearthrate: " + str(data[7]) + "   "
 
-    sys.stdout.write(string)
-    sys.stdout.flush()
-    sys.stdout.write("\b" * len(string))
+    page = ant.plus.pages.parse(data)
+    if isinstance(page, ant.plus.pages.HearthRatePage):
+
+        string = "Hearthrate: " + str(page.get_computed_heath_rate()) + "   "
+
+        sys.stdout.write(string)
+        sys.stdout.flush()
+        sys.stdout.write("\b" * len(string))
 
 
 def main():

It was many years since I actively worked on this package. There is still value in it so I'm happy to see pull requests, but I have less time now to review and do fixes I'm afraid.

I'd be happy to take this in. I think I'd like to see a smaller minimal working example of reading HRM to remain, but besides this and the smaller comments I think it is merge:able.

If you would like to continue on the ant.plus package in this I would be even more happy 😃.

@@ -51,7 +51,7 @@ def __init__(self):

self._datas = queue.Queue()

self.channels = {}
#self.channels = {}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove line


def get_capabilities(self):
data = self.request_message(Message.ID.RESPONSE_CAPABILITIES)
if data[1] == 84:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Message.ID.RESPONSE_CAPABILITIES instead of magic number - but I don't think it should ever be anything else?

def get_capabilities(self):
data = self.request_message(Message.ID.RESPONSE_CAPABILITIES)
if data[1] == 84:
return {"max_channels" : data[2][0], "max_networks" : data[2][1], "options" : data[2][2]}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few more fields here as well, but those can be added later

examples/scan.py Show resolved Hide resolved
@pirower
Copy link
Contributor Author

pirower commented Jul 23, 2020

Hi,
so you would see "plus" as another folder under ant/ (next to easy, base, fs and test)? Ant provides a document (device profile) for the different devices running under Ant+ to guarantee a standard message protocol (e.g. heart rate monitor, frequency sensor for a bike, watt sensor, etc etc..). All these devices have different data pages. Therefor you would need to set the device type (e.g. 120 for heart rate montior) in the channel and decode the data pages according to the device type. I could start with heart rate montior to set a base and keep it open to be completed by other device profiles.

@pirower
Copy link
Contributor Author

pirower commented Jul 23, 2020

Please take a look also in the commit for node_easy and tell me if you want the following functions/fucntionalities included in node:

def remove_deviceNum(self, deviceNum) #only makes sense if a deviceNum has been set in channel
def start(self) (as a thread)
def stop(self): (stop of the thread)
def scan(self, deviceType, timeout = 5, callback=None)

@Tigge
Copy link
Owner

Tigge commented Aug 3, 2020

so you would see "plus" as another folder under ant/ (next to easy, base, fs and test)?

Yes

Ant provides a document (device profile) for the different devices running under Ant+ to guarantee a standard message protocol (e.g. heart rate monitor, frequency sensor for a bike, watt sensor, etc etc..). All these devices have different data pages. Therefor you would need to set the device type (e.g. 120 for heart rate montior) in the channel and decode the data pages according to the device type. I could start with heart rate montior to set a base and keep it open to be completed by other device profiles.

Yes, that is how it would be done. Sounds like a plan!

@Tigge
Copy link
Owner

Tigge commented Aug 3, 2020

Please take a look also in the commit for node_easy and tell me if you want the following functions/fucntionalities included in node:

def remove_deviceNum(self, deviceNum) #only makes sense if a deviceNum has been set in channel
def start(self) (as a thread)
def stop(self): (stop of the thread)
def scan(self, deviceType, timeout = 5, callback=None)

These might be better kept as an example (as it is in this PR now) on how to implement something like that then adding that functionality in node.

@Tigge
Copy link
Owner

Tigge commented Aug 3, 2020

The "Add capabilities to ant and make channels an array instead of a list", "Add new features in channel" and "Add features to node" are ready to commit I think (except the changes requested here). If you want to split this PR those could be merged quickly, but feel free to continue here If you like for the other stuff as well.

@tuna-f1sh
Copy link
Collaborator

Closing/unassigned channels and ANT+ device added with #76

@tuna-f1sh tuna-f1sh closed this Jan 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants