我准备做一种老年人智能设备,可检测跌倒并使用蜂窝网络发送带有位置信息的紧急警报消息。背景随着时间流逝以及社会待遇的提高,我们可以很容易地在街上或商店里找到80到90岁以上的人。近年来,与跌倒相关的危机事件显着增加,其中约80%涉及年龄≥65岁的人。跌倒会导致身心创伤,尤其是对老年人而言。为了提高老年人的生活质量,该项目介绍了跌倒检测和警报系统的开发。大多数老年人(75岁以上)会使用一款功能极少且完全没有通讯设备的基本手机。如果没有智能手机/智能手表和智能应用程序,跟踪他们的活动并不容易。在这个项目中,我构建了一个独立的便携式解决方案,需要一个低维护和低功耗的跌倒检测设备来密切关注他们的活动,以便他们在紧急情况下得到及时的帮助。硬件选择该项目需要一个低功耗、可靠且广泛可用且具有成本效益的蜂窝网络无线电向手机发送警报消息。我将使用BluesWirelessNotecard(用于GPS和蜂窝连接)和带有AA电池线束的BluesWirelessNotecarrierA,这是Notecard的载板,因为锂聚合物电池可能不是老年人可穿戴设备的安全选择。虽然Notecard可以作为独立设备进行跟踪,但我们需要使用EdgeImpulse运行模型推理,因此我将使用RaspberryPiPico作为主机MCU。我使用KiCad设计了一个PCB,它将RaspberryPiPico作为一个模块,仅用作Notecarrier上的一个组件。此防护罩(Piconōto)连接到Notecarrier上,使其成为紧凑且便携的设备,没有任何突出的电线。Piconōto这个名字是两个词pico和nōto的混合体。如下图所示,还使用了许多其他组件。PCB设计这是我的第一个PCB设计,但结果令人满意并且没有任何明显的缺陷。该设计允许从Notecarrier(VMAIN)的主电源或使用焊接跳线垫的3节AA电池电源(VBAT)输入电源。有一个保护二极管可确保RPiPico无法为Notecarrier供电。该设计包括一个Grove连接器、一个Stemma/Qwiic连接器和两个用于连接传感器的通孔接头封装。有一个焊接跳线垫将Notecarrier的ATTN引脚连接到RPiPico3V3_EN引脚(启用/禁用电源)或GPIO引脚7(设置中断)。此外,如果需要,我们可以连接一个按钮和一个LED。大多数Notecard引脚都连接到屏蔽层,可以与RaspberryPiPico一起使用。AUX调试管脚被路由为单独的头管脚,以防我们需要它们来调试Notecard。3D视图预制PCB(前/后)Notecarrier和RaspberryPiPico引脚映射印在PCB背面,方便查找。连接RPiPico直接焊接在SMD焊盘上,以保持其安全并降低整体高度。当RPiPico连接到PC进行编程或监控串行日志时,有一个肖特基二极管用于防止电流从RPiPico流向Notecarrier。红色按钮用作紧急SOS按钮。当长按超过3秒时,会添加一条注释,并通过Notehub将SMS警报消息路由到Twilio。此外,我们需要在NotecarrierATTN与RPiPicoGP7之间以及NotecarrierVBAT与RPiPicoVSYS之间建立一个焊桥(在下图中以红色突出显示)。这样,RPiPico从Notecarrier3xAA电池获取电力,GPIO7引脚配置为中断引脚,并且ATTN引脚配置为在NotecardGPS模块进行定位时触发。最近的GPS位置由主机MCU(RPiPico)存储,以跟踪事件位置。使用Notecard将屏蔽和GroveADXL345加速度计连接到Notecarrier后,最终产品如下所示。开发环境我正在使用EdgeImpulseStudio进行特征生成以及TensorFlowLite模型的创建和训练。您需要在https://studio.edgeimpulse.com注册一个免费帐户并创建一个项目才能开始。我正在使用macOS进行本地开发工作。训练数据集收集各种日常生活活动(ADL)和跌倒的数据是一项耗时且费力的任务。它需要来自不同年龄组的许多人,并且需要大量的工时来管理数据集。幸运的是,有许多高质量的公共数据集可用于类似类型的数据。我使用了SisFall:AFallandMovementDataset,这是使用加速度计获取的跌倒和ADL数据集。该数据集包含19种ADL和15种跌倒。它包括来自38名志愿者的加速度和旋转数据,分为两组:23名19至30岁的成年人和15名60至75岁的老年人。使用三个传感器(2个加速度计和1个陀螺仪)以200Hz的频率样本获取数据。对于这个项目,我使用来自其中一个传感器的加速度数据。此外,我正在使用具有相同配置的相同加速度计ADXL345,用于数据收集。数据集以原始格式提供,可以从该链接下载:https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5298771/数据样本如下:仅使用前3列,它们是来自ADXL345传感器的3轴加速度计数据。34,-259,-74,-38,20,-3,50,-987,-132;36,-258,-73,-36,19,-2,52,-986,-130;32,-257,-68,-37,21,-1,54,-993,-130;37,-260,-73,-36,22,-1,51,-992,-131;34,-259,-74,-34,20,0,51,-992,-128;35,-264,-79,-33,21,-1,52,-991,-133;35,-261,-68,-30,21,0,52,-992,-128;33,-257,-67,-27,21,-1,51,-993,-126;34,-263,-70,-26,21,0,50,-993,-127;35,-261,-76,-24,21,1,51,-994,-130;33,-261,-70,-24,21,0,55,-992,-124;36,-260,-68,-23,20,-1,51,-994,-126;34,-260,-70,-23,20,-2,52,-993,-127;34,-258,-72,-23,19,0,52,-995,-127;35,-260,-69,-23,19,-1,56,-996,-124;37,-262,-73,-23,18,0,51,-988,-124;37,-262,-72,-26,17,-1,49,-996,-127;33,-257,-68,-28,18,-3,51,-991,-127;34,-262,-70,-30,19,-2,52,-990,-121;35,-259,-71,-30,17,-1,55,-991,-125;33,-259,-69,-30,16,-2,50,-989,-122;35,-259,-69,-33,16,-2,52,-993,-129;34,-259,-69,-33,18,-2,53,-984,-124;使用以下转换公式将每个3轴加速度计数据(x,y,z)转换为重力:Resolution:13(13bits)Range:16(+-16g)Acceleration[g]:[(2*Range)/(2^Resolution)]*raw_acceleration将数据上传到EdgeImpulseStudio我们需要创建一个新项目来将数据上传到EdgeImpulseStudio。此外,我们还需要EdgeImpulseStudio项目的HMAC密钥来为数据采集格式生成签名。我们可以从EdgeImpulseStudio仪表板的Dashboard>Keys[tab]复制HMAC密钥。加速度计数据分为ADL和FALL两类,并在上传到EdgeImpulseStudio之前转换为m/s^2。我编写了一个Python脚本(如下),它将原始加速度计数据转换为EdgeImpulse工作室所需的数据采集JSON格式以生成特征。importjsonimporttime,hmac,hashlibimportglobimportosimporttimeHMAC_KEY="YOUR_HMAC_KEY"#Emptysignature(allzeros).HS256gives32bytesignature,andweencodeinhex,soweneed64charactershereemptySignature=''.join(['0']*64)defget_x_filename(filename):m_codes=['D01','D02','D03','D04','D05','D06','D07','D08','D09','D10','D11','D12','D13','D14','D15','D16','D17','D18','D19']f_codes=['F01','F02','F03','F04','F05','F06','F07','F08','F09','F10','F11','F12','F13','F14','F15']code=filename.split('_')[0]label=''ifcodeinm_codes:label='ADL'ifcodeinf_codes:label='FALL'iflabel=='':raiseException('labelnotfound')x_filename='./data/{}.{}.json'.format(label,os.path.splitext(filename)[0])returnx_filenameif__name__=="__main__":files=glob.glob("SisFall_dataset/*/*.txt")CONVERT_G_TO_MS2=9.80665forindex,pathinenumerate(files):filename=os.path.basename(path)values=[]withopen(path)asinfile:forlineininfile:line=line.strip()ifline:row=line.replace("","")cols=row.split(',')ax=((2*16)/(2**13))*float(cols[0])*CONVERT_G_TO_MS2ay=((2*16)/(2**13))*float(cols[1])*CONVERT_G_TO_MS2az=((2*16)/(2**13))*float(cols[2])*CONVERT_G_TO_MS2values.append([ax,ay,az])if(len(values)==0):continuedata={"protected":{"ver":"v1","alg":"HS256","iat":time.time()#epochtime,secondssince1970},"signature":emptySignature,"payload":{"device_name":"aa:bb:ee:ee:cc:ff","device_type":"generic","interval_ms":5,"sensors":[{"name":"accX","units":"m/s2"},{"name":"accY","units":"m/s2"},{"name":"accZ","units":"m/s2"}],"values":values}}#encodeinJSONencoded=json.dumps(data)#signmessagesignature=hmac.new(bytes(HMAC_KEY,'utf-8'),msg=encoded.encode('utf-8'),digestmod=hashlib.sha256).hexdigest()#setthesignatureagaininthemessage,andencodeagaindata['signature']=signatureencoded=json.dumps(data)x_filename=get_x_filename(filename)withopen(x_filename,'w')asfout:fout.write(encoded)要执行上面的脚本,请将其保存到format.py并运行以下命令。假设SisFall数据集已下载到SisFall_dataset目录。$mkdirdata$python3format.py转换后的数据采集JSON如下所示。采样率为200Hz,因此interval_ms设置为5(ms)。{"protected":{"ver":"v1","alg":"HS256","iat":1646227572.4969049},"signature":"3a411ca804ff73ed07d41faf7fb16a8174a58a0bef9adc5cee346f0bc3261e90","payload":{"device_name":"aa:bb:ee:ee:cc:ff","device_type":"generic","interval_ms":5,"sensors":[{"name":"accX","units":"m/s2"},{"name":"accY","units":"m/s2"},{"name":"accZ","units":"m/s2"}],"values":[[0.0383072265625,-9.95987890625,-2.3367408203125],[-0.0383072265625,-9.9215716796875,-2.4516625],[-0.1149216796875,-9.95987890625,-2.375048046875],...]}}使用EdgeImpulseCLI上传数据。请在此处安装CLI:https://docs.edgeimpulse.com/docs/cli-installation。JSON文件由上面的脚本以标签名称为前缀(例如FALL.F10_SA07_R01.json),以便CLI自动推断标签名称。下面的命令用于将所有JSON文件上传到训练数据集:$edge-impulse-uploader--categorytrainingdata/*.json我们本可以使用--categorysplit将数据自动拆分为训练和测试数据集,但我们需要对样本进行分段,以便将其上传到那里以方便。我们可以在EdgeImpulseStudio的数据采集页面中看到上传的数据集。上传的FALL事件数据在跌倒事件之前和之后具有混合的运动事件,这些运动事件通过分割段来删除。ADL类别数据无需任何修改即可使用。我们可以通过选择每个样本并从下拉菜单中单击“拆分样本”来进行拆分,但这是一项耗时且繁琐的工作。幸运的是,有一个EdgeImpulseSDKAPI可用于自动化整个过程。经过一些实验,我们选择了4000ms的分段长度,这是检测跌倒的最佳长度。importjsonimportrequestsimportloggingimportthreadingAPI_KEY="YOUR_API_KEY"projectId="YOUR_PROJECT_ID"headers={"Accept":"application/json","x-api-key":API_KEY}defget_sample_len(sampleId):url=f'https://studio.edgeimpulse.com/v1/api/{projectId}/raw-data/{sampleId}'response=requests.request("GET",url,headers=headers)resp=json.loads(response.text)returnresp['sample']['totalLengthMs']defget_segments(sampleId):url=f'https://studio.edgeimpulse.com/v1/api/{projectId}/raw-data/{sampleId}/find-segments'payload={"shiftSegments":False,"segmentLengthMs":4000}response=requests.request("POST",url,json=payload,headers=headers)returnjson.loads(response.text)["segments"]defcrop_sample(sampleId):sample_len=get_sample_len(sampleId)cropStart=200cropEnd=int(sample_len/5)payload={"cropStart":cropStart,"cropEnd":cropEnd}#print(payload)url=f'https://studio.edgeimpulse.com/v1/api/{projectId}/raw-data/{sampleId}/crop'response=requests.request("POST",url,json=payload,headers=headers)resp=json.loads(response.text)ifresp['success']:logging.info(f'Crop:{sampleId}')else:logging.error(f'Crop:{sampleId}{resp["error"]}')defsegment(tid,ids):forsampleIdinids:try:crop_sample(sampleId)segments=get_segments(sampleId)iflen(segments)>0:payload={"segments":segments}url=f'https://studio.edgeimpulse.com/v1/api/{projectId}/raw-data/{sampleId}/segment'response=requests.request("POST",url,json=payload,headers=headers)resp=json.loads(response.text)ifresp['success']:logging.info(f'Segment:{tid}{sampleId}')else:logging.error(f'Segment:{tid}{sampleId}{resp["error"]}')exceptExceptionase:logging.error(f'Segment:exception{sampleId}')continuedefget_id_list():querystring={"category":"training","excludeSensors":"true","labels":'["FALL"]'}url=f'https://studio.edgeimpulse.com/v1/api/{projectId}/raw-data'response=requests.request("GET",url,headers=headers,params=querystring)resp=json.loads(response.text)id_list=list(map(lambdas:s["id"],resp["samples"]))returnid_listif__name__=="__main__":format="%(asctime)s:%(message)s"logging.basicConfig(format=format,level=logging.INFO,datefmt="%H:%M:%S")id_list=get_id_list()logging.info('SampleCount:{}'.format(len(id_list)))div=8n=int(len(id_list)/div)threads=list()foriinrange(div):ifi==(div-1):ids=id_list[n*i:]else:ids=id_list[n*i:n*(i+1)]x=threading.Thread(target=segment,args=(i,ids))threads.append(x)x.start()forthreadinthreads:thread.join()logging.info("Finished")要执行上面的脚本,请将其保存到segment.py文件并运行以下命令:$python3segments.py分割数据集后,我们可以通过单击EdgeImpulseStudio仪表板上的执行训练/测试拆分按钮将其拆分为训练集和测试集。训练转到ImpulseDesign>CreateImpulse页面,单击Addaprocessingblock,然后选择SpectralAnalysis,这非常适合分析重复运动,例如来自加速度计的数据。此外,在同一页面上,单击Addalearningblock,然后选择Classification(Keras),它从数据中学习模式并将其应用于新数据。我们选择了4000msWindowsize和4000msWindowincrease,这意味着我们正在使用单帧。现在单击保存脉冲按钮。接下来进入ImpulseDesign>SpectralAnalysis页面,如下图所示更改参数,然后单击Saveparameters按钮。单击Saveparameters按钮将重定向到另一个页面,我们应该单击GenerateFeature按钮。完成特征生成通常需要几分钟。我们可以在FeatureExplorer中看到生成的特征的3D可视化。现在转到ImpulseDesign>NeuralNetwork(Keras)页面并定义神经网络架构。我创建了具有2个密集(完全连接)层的模型。训练周期数选择为30。由于ADL和FALL类数据集不平衡,因此选择了自动平衡数据集选项,该选项混合了来自不常见类的更多数据副本,可能有助于使模型对过拟合。现在单击“开始训练”按钮并等待几分钟,直到训练完成。我们可以在下面看到训练输出。量化(int8)模型有97.5%的准确率。测试我们可以通过转到模型测试页面并单击全部分类按钮来在测试数据集上测试模型。该模型在测试数据集上的准确率为97.11%,因此我们有信心该模型应该在真实环境中工作。部署EdgeImpulseStudio支持RaspberryPiPicoC++SDK,但BluesWirelessNotecard尚不支持。幸运的是,两者都支持Arduino库,因此在部署页面我们将选择创建库>Arduino库选项。对于Selectoptimizations选项,我们将选择EnableEONCompiler,这会减少模型的内存使用量。此外,我们将选择量化(Int8)模型。现在单击Build按钮,几秒钟后库包将下载到您的本地计算机。设置BluesWirelessNotecard和Notehub在开始运行应用程序之前,我们应该设置Notecard。以设置带有NotecarrierAA的Notecard,以测试一切是否按预期工作。应用程序代码实际上会在启动时进行Notecard设置,以确保它始终处于已知状态。我们还需要设置Notehub,这是一个云服务,它从Notecard接收数据并允许我们管理设备,并将这些数据路由到我们自己的云应用程序和服务。我们可以在https://notehub.io/sign-up创建一个免费账户,登录成功后我们可以创建一个新项目。复制Notehub用来将Notecard关联到创建的项目的ProjectUID。几次Notehub同步后,我们将能够在Notehub事件页面中看到日志,如下所示。对于SMS警报,我们需要在Twilio上设置一个帐户并通过单击“路线”页面右上角的“创建路线”链接来创建路线。请遵循BluesWireless提供的编写精美的指南中给出的说明,以利用通用HTTP/HTTPS请求/响应路由类型来调用TwilioAPI。在Filters部分中,我们必须指定要路由到Twilio的Notecard出站文件数据。它将确保我们始终发送预期的数据。为了发送SMS消息,TwilioAPI需要具有三个键/值对(Body、From和To)的表单数据。这可以使用JSONata(JSON数据的查询和转换语言)表达式将数据格式化为所需的形式来实现。我们应该在数据>转换字段中选择JSONata表达式,我们可以在文本区域中输入JSONata表达式,如下所示。完整的JSONata表达式如下所示。此表达式将JSON有效负载格式化为TwilioAPI可消费消息格式。该消息由跌倒检测或按钮按下事件生成。根据位置数据的可用性,消息中包含Google地图搜索或方向URL。($locations:=$filter(body.locations,function($v){when-$v.time>3600});$origin:=$count($locations)>0?body.locations[0].lat&","&body.locations[0].lon:"";$waypoints:=$count($locations)>1?$map($locations,function($v,$i){$i>0?$v.lat&','&$v.lon:""}):"";$waypoints:=$filter($waypoints,function($v){$v!=""});$destination:=(where_lat?where_lat:tower_lat)&","&(where_lon?where_lon:tower_lon);$url_search:="https://google.com/maps/search/?api=1";$url_direction:="https://google.com/maps/dir/?api=1";"&Body="&$replace(body.event,"_","")&"at"&($origin=""?$url_search:$url_direction)&($origin=""?"":"%26origin="&$origin)&($count($waypoints)>0?"%26waypoints="&$join($waypoints,"|"):"")&($origin=""?"%26query="&$destination:"%26destination="&$destination)&"&From="&body.from&"&To="&body.to&"&")应用程序工作流程这是应用程序工作流程的高级概述:运行打开ArduinoIDE并通过转到Tools>Board>BoardsManager为RaspberryPiPico安装板包。搜索下图板包并安装:板包安装完成后,在Tools>Board>RaspberryPiRP2040board菜单中选择RaspberryPiPico,在Tools>Port菜单中选择连接板的串口。我们需要使用库管理器(工具>管理库...)安装BluesWirelessNotecard库。如下图所示:此外,我们需要使用库管理器安装RingBuffer库。下面是用于推理的Arduino草图。对于连续运动事件检测,该应用程序使用两个可用的MCU内核,一个用于推理,另一个用于数据采样,这样就不会错过任何事件。#include#include#include#include#include#include#defineserialDebugOutSerial#defineI2C_SDA_PIN2#defineI2C_SCL_PIN3#defineATTN_PIN7#defineEN_PIN22#defineLED_PIN25#defineBTN_PIN28#defineBTN_LONG_PRESS_MS3000#defineMY_PRODUCT_ID"com.xxx.yyy:my_project"#defineFROM_PHONE_NUMBER"+16xxxxxxxx"#defineTO_PHONE_NUMBER"+8xxxxxxxxx"#defineN_LOC5voidbtnISR(void);voidattnISR(void);volatileboolbtnInterruptOccurred=false;volatileboolnotecardAttnFired=false;typedefstruct{doublelat;doublelon;unsignedlongtimestamp;}location_t;RingBuflocations;//Accelerometerdataqueuequeue_tsample_queue;//InitAccelerometerAdafruit_ADXL345_Unifiedaccel=Adafruit_ADXL345_Unified(12345);//NotecardinstanceNotecardnotecard;//Thisbufferisfilledbytheaccelerometerdatafloatsignal_buf[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE];intraw_feature_get_data(size_toffset,size_tlength,float*out_ptr){memcpy(out_ptr,signal_buf+offset,length*sizeof(float));return0;}//InterruptServiceRoutineforBTN_PINtransitionsrisingfromLOWtoHIGHvoidbtnISR(){btnInterruptOccurred=true;}voidattnISR(){notecardAttnFired=true;}voidrestore_notecard(){J*req=notecard.newRequest("card.restore");if(req){JAddBoolToObject(req,"delete",true);if(!notecard.sendRequest(req)){notecard.logDebug("ERROR:restorecardrequest\n");}}else{notecard.logDebug("ERROR:Failedtorestorecard!\n");}}voidsetup_notehub(){//SetupNotehubJ*req=notecard.newRequest("hub.set");if(req){JAddStringToObject(req,"product",MY_PRODUCT_ID);JAddBoolToObject(req,"sync",true);JAddStringToObject(req,"mode","periodic");JAddNumberToObject(req,"outbound",15);//15minsJAddNumberToObject(req,"inbound",60);//60minsif(!notecard.sendRequest(req)){notecard.logDebug("ERROR:SetupNotehubrequest\n");}}else{notecard.logDebug("ERROR:Failedtosetnotehub!\n");}}voidenable_tracking_notecard(){J*req=NoteNewRequest("card.location.mode");if(req){JAddStringToObject(req,"mode","periodic");JAddNumberToObject(req,"seconds",300);if(!notecard.sendRequest(req)){notecard.logDebug("ERROR:card.location.moderequest\n");}J*req=notecard.newRequest("card.location.track");if(req){JAddBoolToObject(req,"start",true);JAddBoolToObject(req,"sync",true);JAddBoolToObject(req,"heartbeat",true);JAddNumberToObject(req,"hours",1);if(!notecard.sendRequest(req)){notecard.logDebug("ERROR:card.location.trackrequest\n");}J*req=NoteNewRequest("card.motion.mode");if(req){JAddBoolToObject(req,"start",true);if(!notecard.sendRequest(req)){notecard.logDebug("ERROR:card.motion.moderequest\n");}}else{notecard.logDebug("ERROR:Failedtosetcardmotionmode!\n");}}else{notecard.logDebug("ERROR:Failedtosetlocationtrack!\n");}}else{notecard.logDebug("ERROR:Failedtosetlocationmode!\n");}}voidregister_location(){J*rsp=notecard.requestAndResponse(notecard.newRequest("card.location"));if(rsp!=NULL){location_tlocation;location.lat=JGetNumber(rsp,"lat");location.lon=JGetNumber(rsp,"lon");location.timestamp=JGetNumber(rsp,"time");notecard.deleteResponse(rsp);notecard.logDebugf("lat=%f,lon=%f\n",location.lat,location.lon);if(locations.isFull()){location_tloc;locations.pop(loc);}locations.push(location);}}voidarm_attn(){//ArmATTNInterruptJ*req=NoteNewRequest("card.attn");if(req){//armATTNifnotalreadyarmedandfirewhenevertheNotecardGPSmodulemakesapositionfix.JAddStringToObject(req,"mode","rearm,location");//JAddStringToObject(req,"mode","sleep");//JAddNumberToObject(req,"seconds",120);if(notecard.sendRequest(req)){notecard.logDebug("ArmATTNinterruptenabled!\n");}else{notecard.logDebug("ERROR:FailedtoarmATTNinterrupt!\n");}}}voidsend_notification(char*event){//AddanoteJ*req=notecard.newRequest("note.add");if(req!=NULL){//sendimmediatelyJAddBoolToObject(req,"sync",true);JAddStringToObject(req,"file","twilio.qo");J*body=JCreateObject();if(body!=NULL){JAddStringToObject(body,"event",event);J*arr=JAddArrayToObject(body,"locations");for(uint8_ti=0;iJ*location=JCreateObject();if(location!=NULL){JAddNumberToObject(location,"lat",locations[i].lat);JAddNumberToObject(location,"lon",locations[i].lon);JAddNumberToObject(location,"time",locations[i].timestamp);JAddItemToObject(arr,"",location);}}JAddStringToObject(body,"from",FROM_PHONE_NUMBER);JAddStringToObject(body,"to",TO_PHONE_NUMBER);JAddItemToObject(req,"body",body);}if(!notecard.sendRequest(req)){notecard.logDebug("ERROR:addnoterequest\n");}}}//Runningoncore0voidsetup(){serialDebugOut.begin(115200);pinMode(LED_PIN,OUTPUT);//while(!serialDebugOut){//delay(250);//}pinMode(EN_PIN,OUTPUT);digitalWrite(EN_PIN,HIGH);//Wait2.5secondsuntilNotecardisreadysleep_ms(10000);digitalWrite(LED_PIN,LOW);//NotecardI2CSDA/SCLisattachedtoRPiPicoGPIO2/3whichusesi2c1/Wire1insteadofdefaulti2c0/WireWire1.setSDA(I2C_SDA_PIN);Wire1.setSCL(I2C_SCL_PIN);Wire1.begin();//AttachButtonInterruptpinMode(BTN_PIN,INPUT);attachInterrupt(digitalPinToInterrupt(BTN_PIN),btnISR,RISING);//AttachNotecardInterruptpinMode(ATTN_PIN,INPUT);attachInterrupt(digitalPinToInterrupt(ATTN_PIN),attnISR,RISING);//InitializeNotecardwithI2Ccommunicationnotecard.begin(NOTE_I2C_ADDR_DEFAULT,NOTE_I2C_MAX_DEFAULT,Wire1);notecard.setDebugOutputStream(serialDebugOut);//RestoreNotecard//restore_notecard();//sleep_ms(100);setup_notehub();sleep_ms(100);//Configurelocationtrackingenable_tracking_notecard();sleep_ms(100);//ArmATTNarm_attn();sleep_ms(1000);}intsample_start=0;intsample_end=EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE/4;intcontinous_fall_detected=0;uint64_tlast_fall_detected_time=0;//Runningoncore0voidloop(){if(notecardAttnFired){notecardAttnFired=false;notecard.logDebug("ATTNfired\n");//Savelocationdataregister_location();//Re-armATTNarm_attn();}if(btnInterruptOccurred){btnInterruptOccurred=false;unsignedlongintstart_time=millis();while(digitalRead(BTN_PIN)==HIGH){if(millis()-start_time>BTN_LONG_PRESS_MS){send_notification("BUTTON_PRESSED");break;}}}ei_impulse_result_tresult={0};signal_tfeatures_signal;features_signal.total_length=EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE;features_signal.get_data=&raw_feature_get_data;//getdatafromthequeuefor(inti=sample_start;iqueue_remove_blocking(&sample_queue,&signal_buf[i]);}if(sample_end==EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE){sample_start=0;sample_end=EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE/4;}else{sample_start=sample_end;sample_end+=EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE/4;}//invoketheimpulseEI_IMPULSE_ERRORres=run_classifier(&features_signal,&result,false);if(res==0){//above80%confidencescoreif(result.classification[1].value>0.8f){ei_printf("Predictions(DSP:%dms.,Classification:%dms.,Anomaly:%dms.):\n",result.timing.dsp,result.timing.classification,result.timing.anomaly);for(size_tix=0;ixei_printf("\t%s:%.5f\n",result.classification[ix].label,result.classification[ix].value);}continous_fall_detected+=1;if(continous_fall_detected>2){send_notification("FALL_DETECTED");continous_fall_detected=0;digitalWrite(LED_PIN,HIGH);}}else{continous_fall_detected=0;//turnofftheledafter5ssincelastfalldetectedif(ei_read_timer_ms()-last_fall_detected_time>=5000){digitalWrite(LED_PIN,LOW);}}}}//Runningoncore1voidsetup1(){if(!accel.begin()){/*TherewasaproblemdetectingtheADXL345...checkyourconnections*/Serial.println("Ooops,noADXL345detected...Checkyourwiring!");while(1);}/*Settherangetowhateverisappropriateforyourproject*/accel.setRange(ADXL345_RANGE_16_G);accel.setDataRate(ADXL345_DATARATE_400_HZ);//addspacefor4additionalsamplestoavoidblockingmainthreadqueue_init(&sample_queue,sizeof(float),EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE+(4*sizeof(float)));//wait10suntilcore0finishessetupandstartsfetchingaccelerometerdatasleep_ms(10000);}uint64_tlast_sample_time=0;//Runningoncore1voidloop1(){sensors_event_tevent;accel.getEvent(&event);floatacceleration[EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME];//readsampleevery5000us=5ms(=200Hz)if(ei_read_timer_us()-last_sample_time>=5000){acceleration[0]=event.acceleration.x;acceleration[1]=event.acceleration.y;acceleration[2]=event.acceleration.z;//ei_printf("%.1f,%.1f,%.1f\n",acceleration[0],acceleration[1],acceleration[2]);for(inti=0;iif(queue_try_add(&sample_queue,&acceleration[i])==false){//ei_printf("Dataqueuefull!\n");sleep_ms(100);break;}}last_sample_time=ei_read_timer_us();}}voidei_printf(constchar*format,...){staticcharprint_buf[1024]={0};va_listargs;va_start(args,format);intr=vsnprintf(print_buf,sizeof(print_buf),format,args);va_end(args);if(r>0){Serial.write(print_buf);}}要运行推理草图,请使用以下命令克隆应用程序存储库:$gitclonehttps://github.com/metanav/Piconoto_SOS.git使用ArduinoIDE中的菜单Sketch>IncludeLibrary>Add.ZIPLibrary导入库包Piconoto_SOS/Fall_Detection_inferencing.zip。通过导航菜单文件>示例>Fall_Detection_inferencing>piconoto_fall_detector打开推理草图并将固件编译/上传到连接的RPiPico板。我们可以使用波特率115200bps的Tools>SerialMonitor来监控推理输出和Notecard调试日志。外观为了保护和方便佩戴,该设备被放置在一个小袋内,可以用腰带将其固定在腰部。此外,小袋材料允许GPS和蜂窝信号穿透。跌倒检测警报消息如果检测到跌倒,我们应该会在5秒内收到如下所示的SMS警报消息。我们可以点击打开谷歌地图的短信中的链接,我们可以知道确切的位置并快速找到该人。由于设备能够捕获最近的GPS位置,因此通过Notehub路由到Twilio的消息中的JSONata表达式包含Google地图方向URL。SOS按钮长按提示信息在下面的屏幕截图中,当GPS无法修复时,无法捕获最近的位置,因此只有蜂窝塔位置通过JSONata表达式包含在Google地图搜索URL中,该表达式通过Notehub路由到Twilio。结论该项目展示了一个易于老年人使用的概念验证设备,并且还展示了简单的神经网络可用于解决以正确方式完成信号处理的复杂问题,同时可以在具有可靠且具有成本效益的位置感知蜂窝网络连接的低功率资源受限设备上运行。如果您对此项目有任何想法、意见或问题,请在下方留言。以上内容翻译自网络,原作者:Naveen,如涉及侵权,可联系删除。