529 lines
21 KiB
Python
529 lines
21 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
'''
|
|
Copyright (C) 2016 Entr'ouvert
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
'''
|
|
|
|
|
|
import os
|
|
import time
|
|
import json
|
|
import logging
|
|
import serial
|
|
import csv
|
|
|
|
from datetime import datetime
|
|
from datetime import date, timedelta
|
|
from time import sleep
|
|
|
|
import whisper
|
|
|
|
import librfid
|
|
import epc
|
|
|
|
import config
|
|
import logging_conf
|
|
import utils
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
MAX_RETRY = 3
|
|
METRICS = ['temperature', 'heartrate']
|
|
|
|
|
|
def try_command(reader, command):
|
|
retry = 0
|
|
while retry < MAX_RETRY:
|
|
try:
|
|
response = reader.device.send(command)
|
|
return response
|
|
except Exception, e:
|
|
logger.warning("Reader request failed with error '{}'".format(e))
|
|
retry += 1
|
|
logger.debug("Remaining tries : {}".format(MAX_RETRY - retry))
|
|
logger.error("Command failed")
|
|
return False
|
|
|
|
|
|
def get_health_parameters(reader):
|
|
logger.debug('Get_HealthParameters')
|
|
quality = ['Disconnected', 'Bad', 'Good']
|
|
command = librfid.ReaderCommand(command_name='Get_HealthParameters',
|
|
reader=reader.device)
|
|
response = try_command(reader, command)
|
|
if not response:
|
|
return False
|
|
health_parameters = response.data
|
|
logger.debug("health_parameters : '{}'".format(health_parameters))
|
|
antennas = []
|
|
for i in range(4):
|
|
tune = "Tune{}".format(i)
|
|
if health_parameters[tune][1] >= 1:
|
|
antennas.append(i)
|
|
logger.debug("Antenna {} connection quality : {} ({})".format(i,
|
|
quality[health_parameters[tune][1]], health_parameters[tune][0]))
|
|
logger.debug("{} antennas connected : '{}'".format(len(antennas), antennas))
|
|
logger.debug("Reader core temperature: %d" % health_parameters['TempCore'])
|
|
logger.debug("Reader power amplificator temperature: %d" % health_parameters['PA'])
|
|
reader.config.set_setting('antenna_connected', antennas)
|
|
return True
|
|
|
|
|
|
def get_rf_settings(reader):
|
|
logger.debug('Get_RFSettings')
|
|
command = librfid.ReaderCommand(command_name='Get_RFSettings',
|
|
reader=reader.device)
|
|
response = try_command(reader, command)
|
|
if not response:
|
|
return False
|
|
i = 0
|
|
for logic_port in response.data:
|
|
if logic_port['ScanDelay'] or logic_port['PowerAnt']:
|
|
logger.debug("Logic port: %s" % i)
|
|
logger.debug("\tScanDelay: %s ms" % logic_port['ScanDelay'])
|
|
logger.debug("\tPowerAnt: %s dBm" % logic_port['PowerAnt'])
|
|
logger.debug("\tAntNb: %s" % logic_port['AntNb'])
|
|
i += 1
|
|
return True
|
|
|
|
|
|
def set_rf_settings(reader, logic_ports):
|
|
logger.debug('Set_RFSettings')
|
|
logger.debug("Logic ports configuration : '{}'".format(logic_ports))
|
|
command = librfid.ReaderCommand(command_name='Set_RFSettings',
|
|
reader=reader.device, data=logic_ports)
|
|
response = try_command(reader, command)
|
|
if not response:
|
|
return False
|
|
return True
|
|
|
|
|
|
class Reader:
|
|
def __init__(self, *args, **kwargs):
|
|
logger.debug('Reader initialization.')
|
|
self.config = config.Config()
|
|
for metric in METRICS:
|
|
fn = "{}.wsp".format(metric)
|
|
setattr(self, "fn_{}".format(metric), fn)
|
|
logger.debug('Metric {} : filename {}.'.format(metric, fn))
|
|
if not os.path.isfile(fn):
|
|
logger.debug('Creating file.')
|
|
whisper.create(fn, [(1, 2592000)])
|
|
else:
|
|
logger.debug('Existing file.')
|
|
|
|
self.csv_temperature = "temperature.csv"
|
|
logger.debug('Metric temperature : filename {}.'.format(self.csv_temperature))
|
|
if not os.path.isfile(self.csv_temperature):
|
|
logger.debug('Creating file {}.'.format(self.csv_temperature))
|
|
with open(fn, 'w') as f:
|
|
writer = csv.writer(f, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
|
|
row = ["date", "time", "int", "degree", "volt"]
|
|
writer.writerow(row)
|
|
else:
|
|
logger.debug('Existing file.')
|
|
self.csv_heartrate = "heartrate.csv"
|
|
logger.debug('Metric heartrate : filename {}.'.format(self.csv_heartrate))
|
|
if not os.path.isfile(self.csv_heartrate):
|
|
logger.debug('Creating file {}.'.format(self.csv_heartrate))
|
|
with open(fn, 'w') as f:
|
|
writer = csv.writer(f, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
|
|
row = ["date", "time", "hr1 int", "hr1 second", "hr1 bpm", "hr2 int", "hr2 second",
|
|
"hr2 bpm", "hr3 int", "hr3 second", "hr3 bpm"]
|
|
writer.writerow(row)
|
|
else:
|
|
logger.debug('Existing file.')
|
|
|
|
self.csv_epc = 'epc.csv'
|
|
if not os.path.isfile(self.csv_epc):
|
|
logger.debug('Creating file.')
|
|
with open(self.csv_epc, 'w') as f:
|
|
writer = csv.writer(f, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
|
|
row = ["date", "time", "epc", "trcal", "can_precision", "sensor_sensibility",
|
|
"sensor_center", "temperature_int", "temperature_degree", "temperature_volt",
|
|
"tension_int", "tension_volt", "cpt_trcal", "period_osc", "hr1_int",
|
|
"hr1_second", "hr1_bpm", "hr2_int", "hr2_second", "hr2_bpm", "hr3_int",
|
|
"hr3_second", "hr3_bpm"]
|
|
writer.writerow(row)
|
|
else:
|
|
logger.debug('Existing file epc.csv.')
|
|
self.config.set_setting('exit_reader', False)
|
|
self.device = librfid.models.RFIDReader()
|
|
|
|
def now(self):
|
|
return int(utils.unix_time(datetime.utcnow()))
|
|
|
|
def write_metric_value(self, metric, value):
|
|
logger.debug('Update {} with value {}.'.format(metric, value))
|
|
whisper.update(getattr(self, "fn_{}".format(metric)), value)
|
|
|
|
def write_metric_value_csv(self, metric, values):
|
|
with open(getattr(self, "csv_{}".format(metric)), 'a') as f:
|
|
writer = csv.writer(f, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
|
|
now = datetime.now()
|
|
for value in values:
|
|
row = [
|
|
now.strftime("%Y/%m/%d"),
|
|
now.strftime("%H:%M:%S"),
|
|
]
|
|
row.extend(value)
|
|
logger.debug('Update csv {} with values {}.'.format(metric, row))
|
|
writer.writerow(row)
|
|
|
|
def write_epc_csv(self, epcs):
|
|
with open(self.csv_epc, 'a') as f:
|
|
writer = csv.writer(f, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
|
|
now = datetime.now()
|
|
for epc in epcs:
|
|
row = [
|
|
now.strftime("%Y/%m/%d"),
|
|
now.strftime("%H:%M:%S"),
|
|
epc.epc,
|
|
epc.trcal,
|
|
epc.can_precision,
|
|
epc.sensor_sensibility,
|
|
epc.sensor_center,
|
|
epc.temperature_int,
|
|
epc.temperature_degree,
|
|
epc.temperature_volt,
|
|
epc.tension_int,
|
|
epc.tension_volt,
|
|
epc.cpt_trcal,
|
|
epc.period_osc,
|
|
epc.hr1_int,
|
|
epc.hr1_second,
|
|
epc.hr1_bpm,
|
|
epc.hr2_int,
|
|
epc.hr2_second,
|
|
epc.hr2_bpm,
|
|
epc.hr3_int,
|
|
epc.hr3_second,
|
|
epc.hr3_bpm,
|
|
]
|
|
logger.debug('Update csv {} with values {}.'.format(self.csv_epc, row))
|
|
writer.writerow(row)
|
|
|
|
def device_ready(self):
|
|
(exit_reader,
|
|
pause_reader,
|
|
self.reader_sleep_short,
|
|
self.temperature,
|
|
self.heartrate) = self.config.reader_config()
|
|
logger.debug('Reader configuration : exit_reader {} - pause_reader {} '
|
|
'- reader_sleep_short {} - temperature {} - heartrate {}'
|
|
''.format(exit_reader, pause_reader, self.reader_sleep_short,
|
|
self.temperature, self.heartrate))
|
|
if exit_reader:
|
|
logger.info('Exit reader asked.')
|
|
exit(0)
|
|
if pause_reader:
|
|
logger.debug('Reader off.')
|
|
logger.debug('Sleep {}ms.'.format(self.reader_sleep_short))
|
|
self.config.set_setting('reader_ready', False)
|
|
return False
|
|
logger.debug('Reader on.')
|
|
try:
|
|
self.device.get_connection()
|
|
logger.debug('Device connected')
|
|
self.config.set_setting('reader_ready', True)
|
|
return True
|
|
except serial.serialutil.SerialException, e:
|
|
self.config.set_setting('reader_ready', False)
|
|
logger.debug('Device not connected : {}.'.format(e))
|
|
return False
|
|
|
|
def device_check(self):
|
|
while not self.device_ready():
|
|
logger.debug('Sleep {}ms.'.format(self.reader_sleep_short))
|
|
sleep(self.reader_sleep_short)
|
|
|
|
if not get_health_parameters(self):
|
|
logger.error('Command get_health_parameters failed, stop tag detection')
|
|
return False
|
|
return True
|
|
|
|
def inventory(self,
|
|
delay=100,
|
|
power=31,
|
|
logic_port=None,
|
|
skip_device_check=False,
|
|
skip_config=False,
|
|
silent=True,
|
|
epcs_csv=False,
|
|
metrics_csv=False,
|
|
metrics_db=False):
|
|
|
|
if not skip_device_check:
|
|
if not self.device_check():
|
|
return None
|
|
|
|
if not skip_config:
|
|
logic_ports = []
|
|
if logic_port is not None:
|
|
logic_ports = [{'ScanDelay': 0, 'PowerAnt': 0, 'AntNb': antenna}
|
|
for antenna in self.config.get_setting('antenna_connected')]
|
|
logic_ports[logic_port] = {
|
|
'ScanDelay': delay,
|
|
'PowerAnt': power,
|
|
'AntNb': self.config.get_setting('antenna_connected')[logic_port]
|
|
}
|
|
else:
|
|
logic_ports = [{'ScanDelay': delay, 'PowerAnt': power, 'AntNb': antenna}
|
|
for antenna in self.config.get_setting('antenna_connected')]
|
|
|
|
if not set_rf_settings(self, logic_ports):
|
|
logger.error('Command set_rf_settings failed, stop tag detection')
|
|
return None
|
|
if not get_rf_settings(self):
|
|
logger.error('Command get_rf_settings failed, stop tag detection')
|
|
return None
|
|
|
|
logger.debug('Inventory')
|
|
command = librfid.Command(command_name='Inventory')
|
|
response = try_command(self, command)
|
|
|
|
if not response:
|
|
logger.error('Command Inventory failed')
|
|
return None
|
|
|
|
if response.data:
|
|
for tag in response.data:
|
|
logger.debug("Tag: %s" % tag['EPC'].encode('hex'))
|
|
logger.debug("\t Read on logic port {} be antenna {}".format(tag['AntID'],
|
|
self.config.get_setting('antenna_connected')[tag['AntID']]))
|
|
logger.debug("\t Times read on all logic ports: %s" % tag['NbRead'])
|
|
epc_decoded = epc.VeadistaEPC(str(tag['EPC'].encode('hex')))
|
|
logger.debug("\n{}".format(epc_decoded))
|
|
|
|
if epcs_csv:
|
|
epcs = [epc.VeadistaEPC(tag['EPC'].encode('hex')) for tag in response.data]
|
|
self.write_epc_csv(epcs)
|
|
|
|
if metrics_csv:
|
|
temperatures = []
|
|
hrs = []
|
|
for tag in response.data:
|
|
e = epc.VeadistaEPC(tag['EPC'].encode('hex'))
|
|
if self.temperature:
|
|
temperatures.append((e.temperature_int,
|
|
e.temperature_degree,
|
|
e.temperature_volt))
|
|
if self.heartrate:
|
|
hrs.append((e.hr1_int, e.hr1_second, e.hr1_bpm, e.hr2_int, e.hr2_second,
|
|
e.hr2_bpm, e.hr3_int, e.hr3_second, e.hr3_bpm))
|
|
if self.temperature:
|
|
logger.debug("Temperature vector : '{}'".format(temperatures))
|
|
self.write_metric_value_csv('temperature', temperatures)
|
|
if self.heartrate:
|
|
logger.debug("Heartrate vector : '{}'".format(hrs))
|
|
self.write_metric_value_csv('heartrate', hrs)
|
|
|
|
if metrics_db:
|
|
if len(response.data) > 0 and self.temperature:
|
|
e = epc.VeadistaEPC(response.data[0]['EPC'].encode('hex'))
|
|
if e.temperature_degree:
|
|
self.write_metric_value('temperature', e.temperature_degree)
|
|
if len(response.data) > 1 and self.heartrate:
|
|
e = epc.VeadistaEPC(response.data[1]['EPC'].encode('hex'))
|
|
if e.hr1_bpm:
|
|
self.write_metric_value('heartrate', e.hr1_bpm)
|
|
|
|
if not silent:
|
|
print "\a"
|
|
|
|
return response
|
|
|
|
def detection(self,
|
|
delay=100,
|
|
power=31,
|
|
logic_port=None,
|
|
silent=True):
|
|
|
|
if not self.device_check():
|
|
return None
|
|
|
|
"""
|
|
As many logic ports as antenna, set all in fast inventory
|
|
"""
|
|
logic_ports = [{'ScanDelay': delay, 'PowerAnt': power, 'AntNb': antenna}
|
|
for antenna in self.config.get_setting('antenna_connected')]
|
|
if not set_rf_settings(self, logic_ports):
|
|
logger.error('Command set_rf_settings failed, stop tag detection')
|
|
return None
|
|
if not get_rf_settings(self):
|
|
logger.error('Command get_rf_settings failed, stop tag detection')
|
|
return None
|
|
|
|
while(True):
|
|
while not self.device_ready():
|
|
logger.debug('Sleep {}ms.'.format(self.reader_sleep_short))
|
|
sleep(self.reader_sleep_short)
|
|
|
|
response = self.inventory(delay=delay, power=power, logic_port=logic_port,
|
|
skip_device_check=True, silent=silent)
|
|
|
|
if not response:
|
|
logger.error('Command Inventory failed, stop tag detection')
|
|
return None
|
|
|
|
if response.data:
|
|
# Veadista assumption is only one physical tag that may return mutiple EPC.
|
|
# So if response.data it is enough to look at the first epc to know the logic port
|
|
logger.info("Tag in the field of logic_port {}".format(response.data[0]['AntID']))
|
|
ant_id = self.config.get_setting('antenna_connected')[response.data[0]['AntID']]
|
|
self.config.set_setting('tag_in_field', ant_id)
|
|
return response.data[0]['AntID']
|
|
else:
|
|
logger.debug('No tag detected')
|
|
self.config.set_setting('tag_in_field', '')
|
|
logger.debug('Sleep {}ms.'.format(self.reader_sleep_short))
|
|
sleep(self.reader_sleep_short)
|
|
|
|
def read(self,
|
|
power=31,
|
|
logic_port=None,
|
|
silent=True,
|
|
epcs_csv=False,
|
|
metrics_csv=False,
|
|
metrics_db=False):
|
|
|
|
logger.debug('Entering reading function.')
|
|
detection = True if logic_port is None else False
|
|
logger.debug('Tag detection : {}.'.format(detection))
|
|
while(True):
|
|
if detection:
|
|
logic_port = self.detection(power=power, silent=silent)
|
|
logger.info('The tag is in the field of logic_port {}.'.format(logic_port))
|
|
if logic_port is None:
|
|
logger.error('The tag detection failed.')
|
|
break
|
|
reader_sleep_long = self.config.get_setting('reader_sleep_long')
|
|
logger.debug('Sleep {} s.'.format(reader_sleep_long))
|
|
sleep(reader_sleep_long)
|
|
self.inventory(delay=4000, power=power, logic_port=logic_port, silent=silent,
|
|
epcs_csv=epcs_csv, metrics_csv=metrics_csv, metrics_db=metrics_db)
|
|
|
|
|
|
class Transmitter():
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
logger.debug('Transmitter initialization.')
|
|
self.config = config.Config()
|
|
for metric in METRICS:
|
|
fn = "{}.wsp".format(metric)
|
|
setattr(self, "fn_{}".format(metric), fn)
|
|
logger.debug('Metric {} : filename {}.'.format(metric, fn))
|
|
self.config.set_setting('exit_transmitter', False)
|
|
|
|
def now(self):
|
|
return int(utils.unix_time(datetime.utcnow()))
|
|
|
|
def set_last_push(self, metric, date):
|
|
with open('last_push_{}'.format(metric), 'w') as f:
|
|
f.write(str(date))
|
|
|
|
def get_last_push(self, metric):
|
|
fn = 'last_push_{}'.format(metric)
|
|
if not os.path.isfile(fn):
|
|
with open(fn, 'w') as f:
|
|
f.write(str(0))
|
|
with open(fn, 'r') as f:
|
|
line = None
|
|
for line in f:
|
|
pass
|
|
if line:
|
|
return int(line)
|
|
return None
|
|
|
|
def send(self):
|
|
while True:
|
|
(exit_transmitter,
|
|
pause_transmitter,
|
|
transmitter_sleep,
|
|
patient_id,
|
|
carbon_hostname,
|
|
carbon_port,
|
|
self.temperature,
|
|
self.heartrate,
|
|
metrics_path) = self.config.transmitter_config()
|
|
|
|
if exit_transmitter:
|
|
logger.info('Exit transmitter asked.')
|
|
exit(0)
|
|
|
|
if pause_transmitter:
|
|
logger.debug('Tranmsitter off.')
|
|
logger.debug('Sleep {}ms.'.format(transmitter_sleep))
|
|
sleep(transmitter_sleep)
|
|
continue
|
|
|
|
logger.debug('Transmitter on.')
|
|
|
|
if not patient_id:
|
|
logger.debug('No patient ID configured.')
|
|
sleep(transmitter_sleep)
|
|
continue
|
|
|
|
logger.debug('Patient ID : {}'.format(patient_id))
|
|
|
|
logger.debug('Carbon host : {}:{}'.format(carbon_hostname, carbon_port))
|
|
|
|
for metric in METRICS:
|
|
if getattr(self, metric):
|
|
logger.debug('Reading metric : {}.'.format(metric))
|
|
fn = getattr(self, "fn_{}".format(metric))
|
|
logger.debug('Filename : {}.'.format(fn))
|
|
if os.path.isfile(fn):
|
|
last_push = _from = self.get_last_push(metric)
|
|
logger.debug('Last read is : {}.'.format(_from))
|
|
until = self.now()
|
|
logger.debug('Until now : {}.'.format(until))
|
|
data = whisper.fetch(fn, _from, until)
|
|
if data:
|
|
start, end, step = data[0]
|
|
cleaned = [(start + i * step, value)
|
|
for i, value in enumerate(data[1]) if value]
|
|
logger.debug('Data to send : {}.'.format(cleaned))
|
|
if cleaned and start == last_push:
|
|
logger.debug("First value already pushed, "
|
|
"we don't send it : {}.".format(cleaned[0]))
|
|
# We remove the first item if pushed last time
|
|
cleaned = cleaned[1:]
|
|
remote_path = "{}.{}.{}".format(metrics_path, patient_id, metric)
|
|
for date, value in cleaned:
|
|
content = "{} {} {}\n".format(remote_path, value, date)
|
|
try:
|
|
logger.debug("Send command : {}.".format(content))
|
|
utils.netcat(carbon_hostname, carbon_port, content)
|
|
last_push = date
|
|
except Exception, e:
|
|
logger.debug("Error executing send command : '{}', "
|
|
"we stopped at : {}.".format(e, last_push))
|
|
break
|
|
logger.debug("Write last push at : {}.".format(last_push))
|
|
self.set_last_push(metric, last_push)
|
|
else:
|
|
logger.debug('No data to send.')
|
|
else:
|
|
logger.debug('No whisper file found.')
|
|
else:
|
|
logger.debug('Sending metric {} off.'.format(metric))
|
|
|
|
logger.debug('Sleep {} s.'.format(transmitter_sleep))
|
|
sleep(transmitter_sleep)
|