我们抽跑步机

最近,我决定为自己进行一次非常奇怪的购买。是的,我买了跑步机。



很快我就意识到,没有像骑自行车那样详细的统计信息。如果是自行车,手机上的应用程序会记录我的速度,心率,脚踏圈速和举重。在培训过程中控制所有这些参数,以便能够查看图表并不时比较结果,这是非常好奇的。

因此,我决定对跑步机执行类似的操作:将其连接到智能手机或平板电脑以收集和显示统计信息。

像往常一样,我的故事是通过传统的文字文章和视频的形式。如您所愿。

视频



文章


设计


即使在我收集跑步机的那一刻,我也注意到遥控器和跑带本身仅连接了四根电线。显然,其中一些用于为控制台供电,因为画布本身已连接至220伏特网络,并且需要其余电线沿相反方向传输控制信号-从控制台到画布,它们控制轨道的速度和角度。

我将示波器与这些导线并联连接,尝试使用不同的组合。

结果,我发现一切都与我预期的相同。一根导线接地,另一根为12伏电源。其余的传输数字数据。

其中之一,信号在切换速度和角度时发生变化。这正是我所需要的!信号幅度约为4伏。但是该协议看起来不像是标准协议,并且信号非常嘈杂,当轨道开启时,您需要以某种方式对其进行过滤。



最后一根导线只是具有恒定频率的脉冲。显然,控制台可以看到与跑带的连接。如果断开此线的连接,遥控器将立即发出错误消息。

这些电线上脉冲传感器发出的指示显然不会传送,但这不是必需的。最好连接一个单独的胸部传感器,骑自行车时我已经使用了很长时间。此外,事实证明,跑步机本身的心率传感器躺在地上,低估了读数。

设备组装


因此,下一个任务是组装一块与这些电线平行连接的板,读取当前的速度和角度,然后以某种方式将其无线传输到平板电脑或智能手机。

我再次决定使用Onion Omega2单板计算机。他必须做得很好。仅需将电源电压降低至3.3伏,并过滤掉干扰数据。

为了降低电压,我现在将这些现成的板与DC-DC转换器一起使用。它们要花费几分钱,可以承受几安培的电流,并且可以通过扭曲来调节输出电压。



同时,该板的结论是直接焊接到另一个板上,非常方便。最主要的是在电路中安装后不要扭曲电压。

为了滤除数据线上的噪声,我制作了一个常规的RC滤波器:一个2.2千欧的电阻和一个22皮法拉的电容器。这样可以滤除高频噪声,留下低频信号。

原来是一条小围巾。



我将其连接到跑步机导线上,以查看信号在打开时的滤波效果如何,显然,波形已变得几乎完美。



内核模块


但是,检查熨斗的性能并不容易。正如我们
之前在示波器上所看到的,信号运行非常快,我们不使用微控制器,而是使用装有Linux的单板Omega2计算机。在Linux下,我们将无法如此迅速地处理来自用户空间的信号。但是从核心来看我们可以!因此,是时候编写Linux内核模块了!

为此,您需要下载Linux内核源代码(在我们的情况下,这是Omega2的OpenWRT程序集),并创建一个目录,其中包含我们模块的源代码。

编写模块代码很像对微控制器进行编程。我们还用C编写,所有内容都是低级的,我们还处理中断,还介绍了GPIO结论。仅在这里,除了上述所有内容之外,我们仍在通过伪文件与用户空间进行交互。因此,我们的内核模块成为硬件和普通应用程序之间的一种适配器。实际上,这称为驱动程序。

起初,我不知道如何解码信号,所以我只是推导出它们的持续时间。



很快就清楚了,信号是用高电平持续时间编码的。长度为600微秒或1200微秒。除初始序列外,低电平始终为600微秒。

总共有17个这样的下降。显然,这是16位数据加上初始序列。我进行了解码,并以此为依据,长的高差是逻辑零,而短的高差是逻辑单位,我就知道发生了什么。我立即看到了我需要的数据!



如您所知,16位是两个字节。第一个字节指示要传输的数据类型:倾斜角度或速度,第二个字节指示数据本身。驱动程序非常简单。

唯一的驱动程序参数是端口号。

/* Module parameters */
static u8 receive_pin = 11;
module_param(receive_pin, byte, S_IRUGO);
MODULE_PARM_DESC(receive_pin,"Treadmill receiver pin number (default 11)");

初始化时,将其配置为输入并设置中断,每次中断级别更改时都会触发该中断。

/* Allocate and init the timer */
data_recv_timer = kzalloc(sizeof(struct hrtimer), GFP_KERNEL);
if (!data_recv_timer) {
    pr_err("treadmill: can't allocate memory for timer\n");
    treadmill_free();
    return -1;
}
hrtimer_init(data_recv_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
data_recv_timer->function = recv_timer_callback;

在这种中断下,我们首先查看当前时间。接下来,我们使用此值来计算自上一次触发中断以来已过去了多少时间,并将其放入数组中。当然,我们会记住当前时间以进行下一次计算。此外,您必须重新启动特殊计时器。

/* IRQ fired every rising/falling edge of receiver pin */
static irq_handler_t treadmill_irq_handler(unsigned int irq,
    void *dev_id, struct pt_regs *regs)
{
    u64 now = ktime_to_us(ktime_get_boottime());
    u8 value = gpio_get_value(receive_pin);
    u64 time_passed;
    reset_recv_timer();

    if ((timings_pos & 1) == value)
    {
        time_passed = now - last_time;
        if (timings_pos < TIMINGS_BUFFER_SIZE)
        {
            timings[timings_pos] = time_passed;
            timings_pos++;
        }
        last_time = now;
    }

    /* Announce that the IRQ has been handled correctly */
    return (irq_handler_t) IRQ_HANDLED;
}

诀窍在于,如果计时器仍然可以工作,则意味着该引脚长时间没有电平下降,因此该是处理收集到的信息的时候了。在计时器调用的函数中,检查是否恰好有34个滴,然后我们查看每个间隔有多长时间。如果有600微秒,然后是1200微秒,那么我们要花900国外;如果间隔较小,则在结果中写入1,然后将其移动一位。处理完每个间隔后,我们将结果发送到打开的伪文件中,从而将数据传输到用户空间。

/* Timer */
static enum hrtimer_restart recv_timer_callback(struct hrtimer *timer)
{
    int i, p;
    u16 data;

    if (timings_pos != 34) {
        pr_debug("treadmill: invalid edges count: %d", timings_pos);
        timings_pos = 0; 
        return HRTIMER_NORESTART;
    }

    data = 0;   
    for (i = 2; i < timings_pos; i += 2)
    {
        data >>= 1;
        if (timings[i] < 900) // 600us = 1, 1200 us = 0
            data |= 0x8000;
    }
    
    for (p = 0; p < 2; p++) {
        for (i = 0; i < treadmill_number_opens; i++) {
            if (!(opened_files[i]->f_mode & FMODE_READ)) continue;
            ((struct cfile_t*)opened_files[i]->private_data)->receiver_buffer[
                ((struct cfile_t*)opened_files[i]->private_data)->receiver_write_pos++
                % RECEIVER_BUFFER_SIZE] = (data >> (8 * p)) & 0xFF;
        }
    };
    wake_up_interruptible(&wq_data);

    timings_pos = 0; 
   
    return HRTIMER_NORESTART;
}

Python服务器和速度检测


然后剩下的就是编写一个Python脚本,该脚本将从伪文件中读取它们并将它们作为JSON字符串通过网络发送。似乎一切都很简单。但是,如果倾斜角度的一切都很简单,而第二个字节中的值恰好与倾斜角度的百分比相对应,那么在速度方面,一切都会变得更加混乱。

值9表示每小时一公里,值160表示每小时18公里。也就是说,数据对实际速度的依赖性一点都不明显。我手动写出所有值,将它们驱入Excel,绘制并绘制出非常不均匀的曲线。



当遥控器上的读数不同时,速度也会有所提高,但是轨道本身的数据和速度却保持不变!例如,5.2 km / h和5.3 km / h实际上是相同的速度。到处都是作弊。我想知道到底有多快?以某种方式对其进行测量,但留待以后使用。

除了将鹦鹉转移到每小时公里之外,该脚本非常简单。我们从Linux伪文件中读取数据,对其进行解码,接受网络连接,并将数据作为JSON字符串传输到通过网络连接的客户端。

class TreadmillServer:
    def __init__(self, device = "/dev/treadmill", port = 11010, interface = '0.0.0.0'):
        self._device = device
        self._port = port
        self._interface = interface
        self._working = False
        self._clients = []
        self._server_sock = None
        self.incline = 0
        self.speed = 0

    def start(self):
        self._working = True
        self._server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._server_sock.bind((self._interface, self._port))
        self._server_sock.listen(10)
        print("Listening port", self._port)
        Thread(target=self._port_listener, name="Treadmill port listener", daemon=True).start()
        Thread(target=self._device_listener, name="Treadmill device listener", daemon=True).start()

    def stop(self):
        self._working = False
        if self._server_sock != None:
            try:
                self._server_sock.close()
            except:
                pass
            self._server_sock = None

    def __del__(self):
        self.stop()

    def _port_listener(self):
        while self._working and self._server_sock:
            try:
                conn, addr = self._server_sock.accept()
                print('Connected: {0}'.format(addr))
                TreadmillClientConnection(self, conn, addr)
            except Exception as e:
                print("Error:", e)

我认为这里不需要授权和安全性。跑步机的状态不是我想要保护免受黑客攻击的数据类型。

我们将此脚本放入启动阶段,然后删除跑步机内部的板。,,它仅适合将控制台连接到跑步带的金属管中。



如您所知,金属屏蔽了无线电信号,因此我将Wi-Fi天线从管道中拿出,但是放在了隐藏电线的塑料外壳下面。



在这台直接的“智能”跑步机上即可使用。她已经知道如何通过网络分发统计信息。只为她写一个客户!

Android客户端


我认为应该是这样的客户。这是一个Android应用程序,我将在平板电脑或智能手机上运行该应用程序,并将其分别放在跑步机本身的显示屏上,它应该在屏幕上显示锻炼中的所有信息,从而代替跑步机本身的显示屏。该应用程序应该能够在后台运行,以便我可以在慢跑时观看视频而不会出现问题。此外,它还应保留运行的详细统计信息,使一切与云同步,并绘制出脉冲对速度和倾斜角度的依赖性图表。

这种应用程序的核心应该是在后台运行,以无限循环连接到跑步机,接收数据并对其进行解码的服务。在这方面没有特别的困难。

心率传感器


最困难的事情是突然使用心率传感器。发现了许多陷阱。我这里有这样的胸部心率监测器:



骑自行车时,我已经使用了很长时间了。它是非常标准的,可以在BLE和蓝牙低功耗蓝牙上工作,可以与手机和Garmin导航仪配对,而不会出现任何问题。我什至不能认为从我的应用程序中使用它会变得如此显而易见。这种传感器具有用于不同读数的标准GUID。

要开始接收心率,您必须首先将心率监视器配置为定期发送读数。我只能通过研究非工作示例并通过键入来做到这一点。
结果,我编写了一个与心率传感器一起使用的类,该类会自动尝试连接到它并定期报告当前心率。

三星健康SDK


至于统计和图表。我决定不重新发明轮子,而是使用骑自行车时已经使用的东西,即以某种方式通过出色的Samsung Health应用结识朋友。

现在可能看起来像是我再次在宣传三星。但是在自行车上,此应用程序确实非常出色。令我惊讶的是,它可以毫无问题地连接到所有传感器,同时显示节奏和车轮速度,并指示耳机中的统计数据,并使用图形显示相同的统计数据,并给出成果并将所有内容存储在云中。

搜索结果显示,三星健康拥有自己的SDK,尽管不是完全可理解,但仍在文档中:img-developer.samsung.com/onlinedocs/health/android/data/index.html

使用它实际上是在使用一个数据库,该数据库存储从采取的步骤和心率测量到血糖和睡眠阶段的各种读数。但是我们现在对运动记录感兴趣,包括运动类型,时间,距离,持续时间,燃烧的卡路里等标量值,以及心率,速度和坐标的历史记录等实时数据。

所有这些数据必须正确存储和准备。需要计算一些。

高度计算


例如,提升高度。从跑步机上,我们知道每个时间点的爬升角度,以百分比为单位。仰角百分比是爬升距离的比例。事实证明,垂直速度等于通常速度乘以斜率的百分比并除以一百。知道了垂直速度,我们可以计算每个时刻的当前高度。因此,尽管在练习中它们没有变化并且没有被考虑,但必须将其输入当前坐标。
根据这些数据,Samsung Health应用程序将显示我应该攀登的高度以及每次训练时的垂直速度。

卡路里计数


但是如何计算卡路里?此外,卡路里计数是三星健康的必需品。同时,燃烧的卡路里是一个非常不准确的指标,它取决于许多不同的因素。不知道对它们进行计数是否有意义。

我没有提出自己的建议,只是用谷歌计算器(https://42.195km.net/e/treadsim/)并从我的javascript(https://42.195km.net/e/treadsim/treadsim107)复制了算法.js)。在入口处,他以行进距离,仰角和重量为准。

我可以手动设置体重,但是由于我们正在与Samsung Health合作,因此我可以从那里获取当前体重。毕竟,我使用小米的智能秤,该智能秤与手机上的Google Fit同步,通过单独的应用程序的Google FIt与Samsung Health同步,通过云的Samsung Health与平板电脑上的自身同步,我的应用程序已经在其中接收了它。

应用外观


在视觉上,该应用程序的任务是大规模显示主要指标:速度,角度,心率,距离,卡路里。最好在黑色背景上以白色进行此操作,以使使用AMOLED屏幕时的电池消耗最少,因为我们明确表示在显示活动时,屏幕应始终打开。



当跑步机处于活动状态时,按钮将自动隐藏。您只能以零速度开始和停止训练。

当然,您需要支持“画中画”模式。只需几行即可完成。您只需要在清单中指出活动支持此模式,并在最小化应用程序时在代码中注明。因此,您可以观看例如YouTube,并在屏幕一角看到跑步机读数。原来很方便。



但是在这个阶段,我终于被Android开发人员的痛苦所取代,因为我已经获得了四种不同的屏幕尺寸:手机和平板电脑处于正常模式,它们也处于“画中画”模式。碰巧的是,如果我为一个屏幕大小选择了正常的字体大小,那么在其他情况下,一切都太小了,然后又太大了。

在为Android开发时,屏幕分为几类,您可以自动为它们应用不同的设置,但就我而言,这还不够。
结果,我不得不计算并设置代码中的字体大小,我认为这是非常错误的。但是,它可以完美地工作。

结果


这就是结果。我们打开应用程序,等待与跑步机和心率传感器的连接,开始训练并照常使用跑步机。
锻炼结束后,我们停止跑步机。达到零速时,将显示“完成训练”按钮。单击它,统计信息将发送到Samsung Health。打开它并查看所有数据。







您可以查看脉搏,速度和上升的图表,比较不同时间间隔的进度,所有这些都存储在云中,并且可以从所有设备访问。

您可以将其与Google Fit同步。美丽。我对结果感到满意。现在最主要的是不要抛出类。您可以添加该应用程序的功能,以便在长时间懒惰时类似于训练。但是我已经懒得去做这个功能。

All Articles