Compare commits
6 Commits
863841bd74
...
f2e9b389b2
Author | SHA1 | Date | |
---|---|---|---|
f2e9b389b2 | |||
8b5643d57d | |||
c52268be2c | |||
40eba650da | |||
9e1f55f7dc | |||
483d054b2f |
18
README.md
Normal file
18
README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Server Setup
|
||||
|
||||
```
|
||||
sudo apt install dnsmasq
|
||||
```
|
||||
|
||||
Edit `/etc/dnsmasq.conf`:
|
||||
|
||||
```
|
||||
no-resolv # for offline / isolated networks only
|
||||
address=/.apsystemsema.com/192.168.69.100
|
||||
address=/.apsema.com/192.168.69.100
|
||||
listen-address=192.168.69.100
|
||||
```
|
||||
|
||||
Replace `192.168.69.100` with the server's IP address.
|
||||
|
||||
Edit router's DHCP DNS settings to use 192.168.69.100 as the name server.
|
111
server/.gitignore
vendored
Normal file
111
server/.gitignore
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
# Editor
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
*.session
|
||||
*.session-journal
|
||||
settings.py
|
||||
|
||||
caseta.crt
|
||||
caseta-bridge.crt
|
||||
caseta.key
|
369
server/APSystemsECUR.py
Normal file
369
server/APSystemsECUR.py
Normal file
|
@ -0,0 +1,369 @@
|
|||
# Stolen from https://github.com/ksheumaker/homeassistant-apsystems_ecur
|
||||
# cheers to ksheumaker and HAEdwin
|
||||
|
||||
import socket
|
||||
import binascii
|
||||
import logging
|
||||
|
||||
class APSystemsInvalidData(Exception):
|
||||
pass
|
||||
|
||||
class APSystemsECUR:
|
||||
def __init__(self, ipaddr, port=8899, raw_ecu=None, raw_inverter=None):
|
||||
self.ipaddr = ipaddr
|
||||
self.port = port
|
||||
|
||||
# what do we expect socket data to end in
|
||||
self.recv_suffix = b'END\n'
|
||||
|
||||
# how long to wait on socket commands until we get our recv_suffix
|
||||
self.timeout = 5
|
||||
|
||||
# how many times do we try the same command in a single update before failing
|
||||
self.cmd_attempts = 3
|
||||
|
||||
# how big of a buffer to read at a time from the socket
|
||||
self.recv_size = 4096*16
|
||||
|
||||
self.qs1_ids = [ '802', '801' ]
|
||||
self.yc600_ids = [ '406', '407', '408', '409' ]
|
||||
self.yc1000_ids = [ '501', '502', '503', '504' ]
|
||||
|
||||
self.cmd_suffix = 'END\n'
|
||||
self.ecu_query = 'APS1100160001' + self.cmd_suffix
|
||||
self.inverter_query_prefix = 'APS1100280002'
|
||||
self.inverter_query_suffix = self.cmd_suffix
|
||||
|
||||
self.inverter_signal_prefix = 'APS1100280030'
|
||||
self.inverter_signal_suffix = self.cmd_suffix
|
||||
|
||||
self.inverter_byte_start = 26
|
||||
|
||||
self.ecu_id = None
|
||||
self.qty_of_inverters = 0
|
||||
self.lifetime_energy = 0
|
||||
self.current_power = 0
|
||||
self.today_energy = 0
|
||||
self.inverters = []
|
||||
self.firmware = None
|
||||
self.timezone = None
|
||||
self.last_update = None
|
||||
|
||||
self.ecu_raw_data = raw_ecu
|
||||
self.inverter_raw_data = raw_inverter
|
||||
self.inverter_raw_signal = None
|
||||
|
||||
self.read_buffer = b''
|
||||
|
||||
self.reader = None
|
||||
self.writer = None
|
||||
|
||||
def query_ecu(self):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect((self.ipaddr,self.port))
|
||||
|
||||
logging.debug('ecu query: %s', self.ecu_query)
|
||||
sock.sendall(self.ecu_query.encode('utf-8'))
|
||||
self.ecu_raw_data = sock.recv(self.recv_size)
|
||||
|
||||
logging.debug('raw ecu data: %s', self.ecu_raw_data)
|
||||
logging.debug('ecu id: %s', self.ecu_raw_data[13:25])
|
||||
power = self.ecu_raw_data[31:35]
|
||||
logging.debug('current power: %s', int.from_bytes(power, byteorder='big'))
|
||||
|
||||
self.process_ecu_data()
|
||||
|
||||
sock.shutdown(socket.SHUT_RDWR)
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect((self.ipaddr,self.port))
|
||||
|
||||
cmd = self.inverter_query_prefix + self.ecu_id + self.inverter_query_suffix
|
||||
logging.debug('inverter data cmd: %s', cmd)
|
||||
sock.sendall(cmd.encode('utf-8'))
|
||||
self.inverter_raw_data = sock.recv(self.recv_size)
|
||||
|
||||
logging.debug('raw inverter data: %s', self.inverter_raw_data)
|
||||
|
||||
sock.shutdown(socket.SHUT_RDWR)
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect((self.ipaddr,self.port))
|
||||
|
||||
cmd = self.inverter_signal_prefix + self.ecu_id + self.inverter_signal_suffix
|
||||
logging.debug('inverter signal cmd: %s', cmd)
|
||||
sock.sendall(cmd.encode('utf-8'))
|
||||
self.inverter_raw_signal = sock.recv(self.recv_size)
|
||||
|
||||
logging.debug('raw signal data: %s', self.inverter_raw_signal)
|
||||
|
||||
sock.shutdown(socket.SHUT_RDWR)
|
||||
sock.close()
|
||||
|
||||
data = self.process_inverter_data()
|
||||
|
||||
data['ecu_id'] = self.ecu_id
|
||||
data['today_energy'] = self.today_energy
|
||||
data['lifetime_energy'] = self.lifetime_energy
|
||||
data['current_power'] = self.current_power
|
||||
|
||||
return(data)
|
||||
|
||||
def aps_int(self, codec, start):
|
||||
try:
|
||||
return int(binascii.b2a_hex(codec[(start):(start+2)]), 16)
|
||||
except ValueError as err:
|
||||
debugdata = binascii.b2a_hex(codec)
|
||||
raise APSystemsInvalidData(f'Unable to convert binary to int location={start} data={debugdata}')
|
||||
|
||||
def aps_short(self, codec, start):
|
||||
try:
|
||||
return int(binascii.b2a_hex(codec[(start):(start+1)]), 8)
|
||||
except ValueError as err:
|
||||
debugdata = binascii.b2a_hex(codec)
|
||||
raise APSystemsInvalidData(f'Unable to convert binary to short int location={start} data={debugdata}')
|
||||
|
||||
def aps_double(self, codec, start):
|
||||
try:
|
||||
return int (binascii.b2a_hex(codec[(start):(start+4)]), 16)
|
||||
except ValueError as err:
|
||||
debugdata = binascii.b2a_hex(codec)
|
||||
raise APSystemsInvalidData(f'Unable to convert binary to double location={start} data={debugdata}')
|
||||
|
||||
def aps_bool(self, codec, start):
|
||||
return bool(binascii.b2a_hex(codec[(start):(start+2)]))
|
||||
|
||||
def aps_uid(self, codec, start):
|
||||
return str(binascii.b2a_hex(codec[(start):(start+12)]))[2:14]
|
||||
|
||||
def aps_str(self, codec, start, amount):
|
||||
return str(codec[start:(start+amount)])[2:(amount+2)]
|
||||
|
||||
def aps_timestamp(self, codec, start, amount):
|
||||
timestr=str(binascii.b2a_hex(codec[start:(start+amount)]))[2:(amount+2)]
|
||||
return timestr[0:4]+'-'+timestr[4:6]+'-'+timestr[6:8]+' '+timestr[8:10]+':'+timestr[10:12]+':'+timestr[12:14]
|
||||
|
||||
def check_ecu_checksum(self, data, cmd):
|
||||
datalen = len(data) - 1
|
||||
logging.debug('datalen: %s', datalen)
|
||||
try:
|
||||
checksum = int(data[5:9])
|
||||
logging.debug('checksum: %s', checksum)
|
||||
except ValueError as err:
|
||||
debugdata = binascii.b2a_hex(data)
|
||||
raise APSystemsInvalidData(f'Error getting checksum int from {cmd} data={debugdata}')
|
||||
|
||||
if datalen != checksum:
|
||||
debugdata = binascii.b2a_hex(data)
|
||||
raise APSystemsInvalidData(f'Checksum on {cmd} failed checksum={checksum} datalen={datalen} data={debugdata}')
|
||||
|
||||
start_str = self.aps_str(data, 0, 3)
|
||||
end_str = self.aps_str(data, len(data) - 4, 3)
|
||||
|
||||
if start_str != 'APS':
|
||||
debugdata = binascii.b2a_hex(data)
|
||||
raise APSystemsInvalidData(f'Result on {cmd} incorrect start signature {start_str} != APS data={debugdata}')
|
||||
|
||||
if end_str != 'END':
|
||||
debugdata = binascii.b2a_hex(data)
|
||||
raise APSystemsInvalidData(f'Result on {cmd} incorrect end signature {end_str} != END data={debugdata}')
|
||||
|
||||
return True
|
||||
|
||||
def process_ecu_data(self, data=None):
|
||||
logging.debug('Processing ECU data...')
|
||||
if not data:
|
||||
data = self.ecu_raw_data
|
||||
|
||||
self.check_ecu_checksum(data, 'ECU Query')
|
||||
|
||||
self.ecu_id = self.aps_str(data, 13, 12)
|
||||
self.qty_of_inverters = self.aps_int(data, 46)
|
||||
self.firmware = self.aps_str(data, 55, 15)
|
||||
self.timezone = self.aps_str(data, 70, 9)
|
||||
self.lifetime_energy = self.aps_double(data, 27) / 10
|
||||
self.today_energy = self.aps_double(data, 35) / 100
|
||||
self.current_power = self.aps_double(data, 31)
|
||||
|
||||
|
||||
def process_signal_data(self, data=None):
|
||||
logging.debug('Processing signal data...')
|
||||
signal_data = {}
|
||||
|
||||
if not data:
|
||||
data = self.inverter_raw_signal
|
||||
|
||||
self.check_ecu_checksum(data, 'Signal Query')
|
||||
|
||||
if not self.qty_of_inverters:
|
||||
return signal_data
|
||||
|
||||
location = 15
|
||||
for i in range(0, self.qty_of_inverters):
|
||||
uid = self.aps_uid(data, location)
|
||||
location += 6
|
||||
|
||||
strength = data[location]
|
||||
location += 1
|
||||
|
||||
strength = int((strength / 255) * 100)
|
||||
signal_data[uid] = strength
|
||||
|
||||
return signal_data
|
||||
|
||||
def process_inverter_data(self, data=None):
|
||||
logging.debug('Processing inverter data...')
|
||||
if not data:
|
||||
data = self.inverter_raw_data
|
||||
|
||||
self.check_ecu_checksum(data, 'Inverter data')
|
||||
|
||||
output = {}
|
||||
|
||||
timestamp = self.aps_timestamp(data, 19, 14)
|
||||
inverter_qty = self.aps_int(data, 17)
|
||||
|
||||
self.last_update = timestamp
|
||||
output['timestamp'] = timestamp
|
||||
output['inverter_qty'] = inverter_qty
|
||||
output['inverters'] = {}
|
||||
|
||||
# this is the start of the loop of inverters
|
||||
location = self.inverter_byte_start
|
||||
|
||||
signal = self.process_signal_data()
|
||||
|
||||
inverters = {}
|
||||
for i in range(0, inverter_qty):
|
||||
|
||||
inv={}
|
||||
|
||||
inverter_uid = self.aps_uid(data, location)
|
||||
inv['uid'] = inverter_uid
|
||||
location += 6
|
||||
|
||||
inv['online'] = self.aps_bool(data, location)
|
||||
location += 1
|
||||
|
||||
inv['unknown'] = self.aps_str(data, location, 2)
|
||||
location += 2
|
||||
|
||||
inv['frequency'] = self.aps_int(data, location) / 10
|
||||
location += 2
|
||||
|
||||
inv['temperature'] = self.aps_int(data, location) - 100
|
||||
location += 2
|
||||
|
||||
inv['signal'] = signal.get(inverter_uid, 0)
|
||||
|
||||
# the first 3 digits determine the type of inverter
|
||||
inverter_type = inverter_uid[0:3]
|
||||
if inverter_type in self.yc600_ids:
|
||||
(channel_data, location) = self.process_yc600(data, location)
|
||||
inv.update(channel_data)
|
||||
|
||||
elif inverter_type in self.qs1_ids:
|
||||
(channel_data, location) = self.process_qs1(data, location)
|
||||
inv.update(channel_data)
|
||||
|
||||
elif inverter_type in self.yc1000_ids:
|
||||
(channel_data, location) = self.process_yc1000(data, location)
|
||||
inv.update(channel_data)
|
||||
|
||||
else:
|
||||
raise APSystemsInvalidData(f'Unsupported inverter type {inverter_type}')
|
||||
|
||||
inverters[inverter_uid] = inv
|
||||
|
||||
output['inverters'] = inverters
|
||||
return (output)
|
||||
|
||||
def process_yc1000(self, data, location):
|
||||
|
||||
power = []
|
||||
voltages = []
|
||||
|
||||
power.append(self.aps_int(data, location))
|
||||
location += 2
|
||||
|
||||
voltage = self.aps_int(data, location)
|
||||
location += 2
|
||||
|
||||
power.append(self.aps_int(data, location))
|
||||
location += 2
|
||||
|
||||
voltage = self.aps_int(data, location)
|
||||
location += 2
|
||||
|
||||
power.append(self.aps_int(data, location))
|
||||
location += 2
|
||||
|
||||
voltage = self.aps_int(data, location)
|
||||
location += 2
|
||||
|
||||
power.append(self.aps_int(data, location))
|
||||
location += 2
|
||||
|
||||
voltages.append(voltage)
|
||||
|
||||
output = {
|
||||
'model' : 'YC1000',
|
||||
'channel_qty' : 4,
|
||||
'power' : power,
|
||||
'voltage' : voltages
|
||||
}
|
||||
|
||||
return (output, location)
|
||||
|
||||
|
||||
def process_qs1(self, data, location):
|
||||
|
||||
power = []
|
||||
voltages = []
|
||||
|
||||
power.append(self.aps_int(data, location))
|
||||
location += 2
|
||||
|
||||
voltage = self.aps_int(data, location)
|
||||
location += 2
|
||||
|
||||
power.append(self.aps_int(data, location))
|
||||
location += 2
|
||||
|
||||
power.append(self.aps_int(data, location))
|
||||
location += 2
|
||||
|
||||
power.append(self.aps_int(data, location))
|
||||
location += 2
|
||||
|
||||
voltages.append(voltage)
|
||||
|
||||
output = {
|
||||
'model' : 'QS1',
|
||||
'channel_qty' : 4,
|
||||
'power' : power,
|
||||
'voltage' : voltages
|
||||
}
|
||||
|
||||
return (output, location)
|
||||
|
||||
|
||||
def process_yc600(self, data, location):
|
||||
power = []
|
||||
voltages = []
|
||||
|
||||
for i in range(0, 2):
|
||||
power.append(self.aps_int(data, location))
|
||||
location += 2
|
||||
|
||||
voltages.append(self.aps_int(data, location))
|
||||
location += 2
|
||||
|
||||
output = {
|
||||
'model' : 'YC600',
|
||||
'channel_qty' : 2,
|
||||
'power' : power,
|
||||
'voltage' : voltages,
|
||||
}
|
||||
|
||||
return (output, location)
|
170
server/main.py
Normal file
170
server/main.py
Normal file
|
@ -0,0 +1,170 @@
|
|||
import os
|
||||
import logging
|
||||
logging.basicConfig(
|
||||
format='[%(asctime)s] %(levelname)s %(module)s/%(funcName)s: - %(message)s',
|
||||
level=logging.DEBUG if os.environ.get('DEBUG') else logging.INFO)
|
||||
logging.getLogger('aiohttp').setLevel(logging.DEBUG if os.environ.get('DEBUG') else logging.WARNING)
|
||||
|
||||
import asyncio
|
||||
from aiohttp import web
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
|
||||
from APSystemsECUR import APSystemsECUR
|
||||
|
||||
ECU_IP = '192.168.69.153'
|
||||
LISTEN_IP = '192.168.69.100'
|
||||
ecu = APSystemsECUR(ECU_IP)
|
||||
app = web.Application()
|
||||
prev_ecu_timestamp = None
|
||||
solar_data = {}
|
||||
|
||||
from influxdb import InfluxDBClient
|
||||
client = InfluxDBClient('localhost', 8086, database='solar2')
|
||||
|
||||
async def proxy(reader, writer):
|
||||
message = await reader.read(1024)
|
||||
addr = writer.get_extra_info('peername')
|
||||
|
||||
try:
|
||||
logging.debug('Recvd from {}: {}'.format(addr[0], message))
|
||||
offset = 32
|
||||
date_time_obj = datetime.strptime(str(message)[offset:offset+14], '%Y%m%d%H%M%S') + timedelta(minutes=-5)
|
||||
send_str = '101' + datetime.strftime(date_time_obj, '%Y%m%d%H%M%S')
|
||||
send_data = send_str.encode()
|
||||
logging.debug('Sending to {}: {}'.format(addr[0], send_data))
|
||||
writer.write(send_data)
|
||||
await writer.drain()
|
||||
except ValueError:
|
||||
logging.debug('Ignored unnecessary data')
|
||||
|
||||
writer.close()
|
||||
|
||||
async def run_proxies():
|
||||
for port in [8995, 8996, 8997]:
|
||||
server = await asyncio.start_server(proxy, LISTEN_IP, port)
|
||||
logging.info('Started TCP listener server on %s:%s', LISTEN_IP, port)
|
||||
task = asyncio.create_task(server.serve_forever())
|
||||
|
||||
async def get_data():
|
||||
global prev_ecu_timestamp
|
||||
global solar_data
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
while True:
|
||||
try:
|
||||
logging.debug('Grabbing ECU data...')
|
||||
data = ecu.query_ecu()
|
||||
logging.debug('Good read, timestamp: %s Mountain Time, current power: %s', data['timestamp'], data['current_power'])
|
||||
|
||||
if data['timestamp'] != prev_ecu_timestamp:
|
||||
total = 0
|
||||
timestamp = datetime.utcnow()
|
||||
|
||||
for i in data['inverters'].values():
|
||||
total += i['power'][0]
|
||||
total += i['power'][1]
|
||||
data['actual_total'] = total
|
||||
|
||||
solar_data = data
|
||||
logging.info('Solar data updated, ecu time: %s Mountain, ecu total: %s, actual total: %s', data['timestamp'], data['current_power'], total)
|
||||
|
||||
points = []
|
||||
for i in data['inverters'].values():
|
||||
points.append({
|
||||
'time': timestamp,
|
||||
'measurement': 'inverter',
|
||||
'tags': {'ecu': data['ecu_id'], 'inverter': i['uid']},
|
||||
'fields': {
|
||||
'ecu_time': data['timestamp'],
|
||||
'online': i['online'],
|
||||
'frequency': i['frequency'],
|
||||
'temperature': i['temperature'],
|
||||
}
|
||||
})
|
||||
|
||||
points.append({
|
||||
'time': timestamp,
|
||||
'measurement': 'panel',
|
||||
'tags': {'ecu': data['ecu_id'], 'inverter': i['uid'], 'channel': '0'},
|
||||
'fields': {
|
||||
'ecu_time': data['timestamp'],
|
||||
'power': i['power'][0],
|
||||
'voltage': i['voltage'][0]
|
||||
}
|
||||
})
|
||||
|
||||
points.append({
|
||||
'time': timestamp,
|
||||
'measurement': 'panel',
|
||||
'tags': {'ecu': data['ecu_id'], 'inverter': i['uid'], 'channel': '1'},
|
||||
'fields': {
|
||||
'ecu_time': data['timestamp'],
|
||||
'power': i['power'][1],
|
||||
'voltage': i['voltage'][1]
|
||||
}
|
||||
})
|
||||
|
||||
points.append({
|
||||
'time': timestamp,
|
||||
'measurement': 'ecu',
|
||||
'tags': {'ecu': data['ecu_id']},
|
||||
'fields': {
|
||||
'ecu_total': data['current_power'],
|
||||
'ecu_time': data['timestamp'],
|
||||
'actual_total': data['actual_total'],
|
||||
'today_energy': data['today_energy'],
|
||||
'lifetime_energy': data['lifetime_energy'],
|
||||
}
|
||||
})
|
||||
|
||||
client.write_points(points)
|
||||
|
||||
logging.info('Wrote %s points to InfluxDB', len(points))
|
||||
|
||||
|
||||
prev_ecu_timestamp = data['timestamp']
|
||||
|
||||
except Exception as err:
|
||||
logging.error('Error: ' + str(err))
|
||||
|
||||
await asyncio.sleep(120)
|
||||
|
||||
async def index(request):
|
||||
return web.Response(text='hello world', content_type='text/html')
|
||||
|
||||
async def data(request):
|
||||
return web.json_response(solar_data)
|
||||
|
||||
async def display(request):
|
||||
res = dict(power=solar_data['actual_total'], brightness=5)
|
||||
return web.json_response(res)
|
||||
|
||||
async def history(request):
|
||||
try:
|
||||
date = datetime.strptime(request.match_info['date'], '%Y-%m-%d')
|
||||
tz = pytz.timezone('America/Edmonton')
|
||||
date = tz.localize(date)
|
||||
except ValueError:
|
||||
raise web.HTTPNotFound
|
||||
|
||||
start = int(date.timestamp())
|
||||
end = date + timedelta(days=1)
|
||||
end = int(end.timestamp())
|
||||
|
||||
q = 'select * from ecu where time >= {}s and time < {}s'.format(start, end)
|
||||
history = list(client.query(q).get_points())
|
||||
|
||||
return web.json_response(history)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.router.add_get('/', index)
|
||||
app.router.add_get('/data', data)
|
||||
app.router.add_get('/display', display)
|
||||
app.router.add_get('/history/{date}', history)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(run_proxies())
|
||||
loop.create_task(get_data())
|
||||
web.run_app(app, port=6901)
|
18
server/requirements.txt
Normal file
18
server/requirements.txt
Normal file
|
@ -0,0 +1,18 @@
|
|||
aiohttp==3.7.4.post0
|
||||
async-timeout==3.0.1
|
||||
attrs==21.2.0
|
||||
certifi==2021.5.30
|
||||
chardet==4.0.0
|
||||
ciso8601==2.1.3
|
||||
idna==2.10
|
||||
influxdb==5.3.1
|
||||
msgpack==1.0.2
|
||||
multidict==5.1.0
|
||||
pkg-resources==0.0.0
|
||||
python-dateutil==2.8.1
|
||||
pytz==2021.1
|
||||
requests==2.25.1
|
||||
six==1.16.0
|
||||
typing-extensions==3.10.0.0
|
||||
urllib3==1.26.5
|
||||
yarl==1.6.3
|
Loading…
Reference in New Issue
Block a user