Skip to content

Commit bf18b8f

Browse files
committed
乐动模块
1 parent f3a24a4 commit bf18b8f

6 files changed

Lines changed: 698 additions & 0 deletions

File tree

port/modules/ATGM336H_5N.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
from machine import UART, Pin
2+
import time
3+
4+
class GPS():
5+
def __init__(self,uart_num=1, pin_tx=Pin.P14, pin_rx=Pin.P13):
6+
self.__uart_port = UART(uart_num, baudrate=9600, tx=pin_tx, rx=pin_rx)
7+
self.GNSS_RX_Buffer = ""
8+
self.GNSS_Buffer = ""
9+
self.UTC_Time = ""
10+
self.latitude = ""
11+
self.N_S = ""
12+
self.longitude = ""
13+
self.E_W = ""
14+
self.speed_to_groud = ""
15+
self.speed_to_groud_kh = 0
16+
self.course_over_ground = ""
17+
self.date = ""
18+
self.isDecodeData = False
19+
self.isParseData = False
20+
self.DataIsUseful = False # 是否有有效数据信息
21+
22+
def GNSS_Read(self):
23+
self.GNSS_RX_Buffer = self.__uart_port.read(1200)
24+
while type(self.GNSS_RX_Buffer)!=bytes:
25+
self.GNSS_RX_Buffer = self.__uart_port.read(1200)
26+
27+
# 由于电路连接不可靠等原因,有时接收到的信息含有非 ASCII 字符,引发 UnicodeError
28+
# AttributeError 引发原因未知
29+
while self.isDecodeData==False:
30+
try:
31+
self.GNSS_Buffer = self.GNSS_RX_Buffer.decode('ASCII')
32+
self.isDecodeData = True
33+
except UnicodeError:
34+
self.GNSS_RX_Buffer = self.__uart_port.read(1200)
35+
except AttributeError:
36+
self.GNSS_RX_Buffer = self.__uart_port.read(1200)
37+
38+
# 测试模块工作是否正常的代码,实际应用请注释掉以避免输出信息过多
39+
#print("This is GNSS_RX_Buffer, origin data from uart:")
40+
#print(self.GNSS_RX_Buffer)
41+
#print("This is GNSS_Buffer, after decoding:")
42+
#print(self.GNSS_Buffer)
43+
44+
# 模块输出的信息很多,为了简便起见我们只选择 RMC 最小定位信息,此部分代码用于从 GNSS_Buffer 中截取 RMC 部分
45+
GNSS_BufferHead = self.GNSS_Buffer.find("$GPRMC,")
46+
if GNSS_BufferHead == -1:
47+
GNSS_BufferHead = self.GNSS_Buffer.find("GNRMC,")
48+
#print(GNSS_BufferHead)
49+
if GNSS_BufferHead == -1:
50+
print("Cannot read the GPS , RMC imformation")
51+
self.isDecodeData = False
52+
else:
53+
GNSS_BufferTail = self.GNSS_Buffer[GNSS_BufferHead:].find("\r\n")
54+
#print(GNSS_BufferTail)
55+
if GNSS_BufferTail == -1:
56+
print("Not end with newline")
57+
self.isDecodeData = False
58+
59+
self.GNSS_Buffer=self.GNSS_Buffer[GNSS_BufferHead:GNSS_BufferHead+GNSS_BufferTail]
60+
61+
# 测试模块工作是否正常的代码,实际应用请注释掉以避免输出信息过多
62+
# print("This is GNSS_RX_Buffer, origin data from uart:")
63+
# print(self.GNSS_RX_Buffer)
64+
# print("This is GNSS_Buffer, including RMC info:")
65+
# print(self.GNSS_Buffer)
66+
67+
def GNSS_Parese(self):
68+
if(self.isDecodeData == True):
69+
self.isDecodeData= False
70+
print("*****************************************")
71+
72+
temp = self.GNSS_Buffer.split(',')
73+
74+
try:
75+
# RMC 信息中自带的标识符, A 代表有效, V 代表无效
76+
if temp[2] == 'A':
77+
self.DataIsUseful = True
78+
else:
79+
self.DataIsUseful = False
80+
81+
if temp[1] == "":
82+
self.UTC_Time = "-1"
83+
else:
84+
self.UTC_Time = temp[1]
85+
self.UTC_Time_Beijing = str(int(self.UTC_Time[0:2])+8)+':'+self.UTC_Time[2:4]+':'+self.UTC_Time[4:6]
86+
self.UTC_Time = self.UTC_Time[0:2]+':'+self.UTC_Time[2:4]+':'+self.UTC_Time[4:6]
87+
88+
if temp[3] == "":
89+
self.latitude = "-1"
90+
else:
91+
self.latitude = temp[3]
92+
self.latitude = self.latitude[0:2]+' degree '+self.latitude[2:]+'\''
93+
94+
self.N_S = temp[4]
95+
96+
if temp[5] == "":
97+
self.longitude = "-1"
98+
else:
99+
self.longitude = temp[5]
100+
#self.longitude = self.longitude[0:3]+'°'+self.latitude[3:]
101+
self.longitude = self.longitude[0:3]+' degree '+self.longitude[3:]+'\''
102+
103+
self.E_W = temp[6]
104+
105+
# RMC 中的速度以节为单位
106+
if temp[7] != "":
107+
try:
108+
self.speed_to_groud = float(temp[7])
109+
self.speed_to_groud_kh = self.speed_to_groud*1.852
110+
except ValueError:
111+
pass
112+
else:
113+
self.speed_to_groud = -1
114+
self.speed_to_groud_kh = -1
115+
116+
if temp[8] != "":
117+
try:
118+
self.course_over_ground = float(temp[8])
119+
except ValueError:
120+
pass
121+
else:
122+
self.course_over_ground = -1
123+
124+
if temp[9] == "":
125+
self.date = "-1"
126+
else:
127+
self.date = temp[9]
128+
self.date = self.date[4:6]+'.'+self.date[2:4]+'.'+self.date[0:2]
129+
130+
self.isParseData = True
131+
except IndexError:
132+
pass
133+
134+
def print_GNSS_info(self):
135+
if(self.isParseData):
136+
self.isParseData = False
137+
if(self.DataIsUseful):
138+
print("维度: %s %s",self.latitude,self.N_S)
139+
print("经度: %s %s",self.longitude,self.E_W)
140+
print("日期: %s",self.date,end=' ')
141+
print("UTC时间: %s",self.UTC_Time)
142+
print("卫星对地速度: %f km/h",self.speed_to_groud_kh)
143+
# print("course_over_groud: %f°",self.course_over_ground)
144+
else:
145+
print(" !")

port/modules/bluebit.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333

3434
from spl06_001 import Barometric
3535
from apds9960 import Gesture
36+
from ATGM336H_5N import GPS
37+
from weather import WEATHER
38+
from pm25 import PM25
39+
from max30102 import MAX30102
3640

3741
class Thermistor:
3842
"""

port/modules/hrcalc.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# 25 samples per second (in algorithm.h)
2+
SAMPLE_FREQ = 25
3+
#计算HR时取4个样本的移动平均值
4+
MA_SIZE = 4
5+
#采样频率
6+
BUFFER_SIZE = 100
7+
8+
#取平均值
9+
10+
def get_mean(ls):
11+
return sum(ls)/len(ls)
12+
13+
def calc_hr_and_spo2(ir_data, red_data):
14+
15+
ir_mean = int(get_mean(ir_data))
16+
x = []
17+
for k in ir_data:
18+
x.append((k-ir_mean)*-1)
19+
m = len(x) - MA_SIZE
20+
for i in range(m):
21+
x[i] = sum(x[i:i+MA_SIZE]) / MA_SIZE
22+
23+
#计算阈值
24+
n_th = int(get_mean(x))
25+
n_th = 30 if n_th < 30 else n_th #允许的最小值
26+
n_th = 60 if n_th > 60 else n_th #允许的最大值
27+
28+
ir_valley_locs, n_peaks = find_peaks(x, BUFFER_SIZE, n_th, 4, 15)
29+
peak_interval_sum = 0
30+
if n_peaks >= 2:
31+
for i in range(1, n_peaks):
32+
peak_interval_sum += (ir_valley_locs[i] - ir_valley_locs[i-1])
33+
peak_interval_sum = int(peak_interval_sum / (n_peaks - 1))
34+
hr = int(SAMPLE_FREQ * 60 / peak_interval_sum)
35+
hr_valid = True
36+
else:
37+
hr = -999 #错误值
38+
hr_valid = False
39+
40+
# ---------spo2 血氧浓度---------
41+
exact_ir_valley_locs_count = n_peaks
42+
for i in range(exact_ir_valley_locs_count):
43+
if ir_valley_locs[i] > BUFFER_SIZE:
44+
spo2 = -999
45+
spo2_valid = False
46+
return hr, hr_valid, spo2, spo2_valid
47+
48+
i_ratio_count = 0
49+
ratio = []
50+
51+
# find max between two valley locations 在两个山谷位置之间找到最大值
52+
# and use ratio between AC component of Ir and Red DC component of Ir and Red for SpO2 以及Ir的AC分量与Ir和Red的Red DC分量对于SpO2的使用比率
53+
red_dc_max_index = -1
54+
ir_dc_max_index = -1
55+
for k in range(exact_ir_valley_locs_count-1):
56+
red_dc_max = -16777216
57+
ir_dc_max = -16777216
58+
if ir_valley_locs[k+1] - ir_valley_locs[k] > 3:
59+
for i in range(ir_valley_locs[k], ir_valley_locs[k+1]):
60+
if ir_data[i] > ir_dc_max:
61+
ir_dc_max = ir_data[i]
62+
ir_dc_max_index = i
63+
if red_data[i] > red_dc_max:
64+
red_dc_max = red_data[i]
65+
red_dc_max_index = i
66+
67+
red_ac = int((red_data[ir_valley_locs[k+1]] - red_data[ir_valley_locs[k]]) * (red_dc_max_index - ir_valley_locs[k]))
68+
red_ac = red_data[ir_valley_locs[k]] + int(red_ac / (ir_valley_locs[k+1] - ir_valley_locs[k]))
69+
red_ac = red_data[red_dc_max_index] - red_ac # subtract linear DC components from raw 从原始中减去线性直流分量
70+
71+
ir_ac = int((ir_data[ir_valley_locs[k+1]] - ir_data[ir_valley_locs[k]]) * (ir_dc_max_index - ir_valley_locs[k]))
72+
ir_ac = ir_data[ir_valley_locs[k]] + int(ir_ac / (ir_valley_locs[k+1] - ir_valley_locs[k]))
73+
ir_ac = ir_data[ir_dc_max_index] - ir_ac # subtract linear DC components from raw
74+
75+
nume = red_ac * ir_dc_max
76+
denom = ir_ac * red_dc_max
77+
if (denom > 0 and i_ratio_count < 5) and nume != 0:
78+
# original cpp implementation uses overflow intentionally.
79+
# but at 64-bit OS, Pyhthon 3.X uses 64-bit int and nume*100/denom does not trigger overflow
80+
# so using bit operation ( &0xffffffff ) is needed
81+
ratio.append(int(((nume * 100) & 0xffffffff) / denom))
82+
i_ratio_count += 1
83+
84+
# choose median value since PPG signal may vary from beat to beat
85+
ratio = sorted(ratio) # sort to ascending order
86+
mid_index = int(i_ratio_count / 2)
87+
88+
ratio_ave = 0
89+
if mid_index > 1:
90+
ratio_ave = int((ratio[mid_index-1] + ratio[mid_index])/2)
91+
else:
92+
if len(ratio) != 0:
93+
ratio_ave = ratio[mid_index]
94+
95+
# why 184?
96+
# print("ratio average: ", ratio_ave)
97+
if ratio_ave > 2 and ratio_ave < 184:
98+
# -45.060 * ratioAverage * ratioAverage / 10000 + 30.354 * ratioAverage / 100 + 94.845
99+
spo2 = -45.060 * (ratio_ave**2) / 10000.0 + 30.054 * ratio_ave / 100.0 + 94.845
100+
spo2_valid = True
101+
else:
102+
spo2 = -999
103+
spo2_valid = False
104+
105+
return hr, hr_valid, spo2, spo2_valid
106+
107+
108+
def find_peaks(x, size, min_height, min_dist, max_num):
109+
"""
110+
Find at most MAX_NUM peaks above MIN_HEIGHT separated by at least MIN_DISTANCE
111+
"""
112+
ir_valley_locs, n_peaks = find_peaks_above_min_height(x, size, min_height, max_num)
113+
ir_valley_locs, n_peaks = remove_close_peaks(n_peaks, ir_valley_locs, x, min_dist)
114+
115+
n_peaks = min([n_peaks, max_num])
116+
117+
return ir_valley_locs, n_peaks
118+
119+
120+
def find_peaks_above_min_height(x, size, min_height, max_num):
121+
"""
122+
Find all peaks above MIN_HEIGHT
123+
"""
124+
125+
i = 0
126+
n_peaks = 0
127+
ir_valley_locs = [] # [0 for i in range(max_num)]
128+
while i < size - 1:
129+
if x[i] > min_height and x[i] > x[i-1]: # find the left edge of potential peaks
130+
n_width = 1
131+
# original condition i+n_width < size may cause IndexError
132+
# so I changed the condition to i+n_width < size - 1
133+
while i + n_width < size - 1 and x[i] == x[i+n_width]: # find flat peaks
134+
n_width += 1
135+
if x[i] > x[i+n_width] and n_peaks < max_num: # find the right edge of peaks
136+
# ir_valley_locs[n_peaks] = i
137+
ir_valley_locs.append(i)
138+
n_peaks += 1 # original uses post increment
139+
i += n_width + 1
140+
else:
141+
i += n_width
142+
else:
143+
i += 1
144+
145+
return ir_valley_locs, n_peaks
146+
147+
148+
def remove_close_peaks(n_peaks, ir_valley_locs, x, min_dist):
149+
"""
150+
Remove peaks separated by less than MIN_DISTANCE
151+
"""
152+
153+
# should be equal to maxim_sort_indices_descend
154+
# order peaks from large to small
155+
# should ignore index:0
156+
sorted_indices = sorted(ir_valley_locs, key=lambda i: x[i])
157+
sorted_indices.reverse()
158+
159+
# this "for" loop expression does not check finish condition
160+
# for i in range(-1, n_peaks):
161+
i = -1
162+
while i < n_peaks:
163+
old_n_peaks = n_peaks
164+
n_peaks = i + 1
165+
# this "for" loop expression does not check finish condition
166+
# for j in (i + 1, old_n_peaks):
167+
j = i + 1
168+
while j < old_n_peaks:
169+
n_dist = (sorted_indices[j] - sorted_indices[i]) if i != -1 else (sorted_indices[j] + 1) # lag-zero peak of autocorr is at index -1
170+
if n_dist > min_dist or n_dist < -1 * min_dist:
171+
sorted_indices[n_peaks] = sorted_indices[j]
172+
n_peaks += 1 # original uses post increment
173+
j += 1
174+
i += 1
175+
176+
sorted_indices[:n_peaks] = sorted(sorted_indices[:n_peaks])
177+
178+
return sorted_indices, n_peaks

0 commit comments

Comments
 (0)