aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--LICENSE339
-rw-r--r--README.md2
-rw-r--r--app.py62
-rw-r--r--app_requirements.txt4
-rw-r--r--cron_Dockerfile17
-rw-r--r--cron_requirements.txt6
-rw-r--r--database.py168
-rw-r--r--devices.py75
-rw-r--r--docker-compose.yml24
-rw-r--r--entrypoint.sh4
-rw-r--r--mikrotik.py99
-rw-r--r--power.env.example13
-rw-r--r--static/scripts.js83
-rw-r--r--static/style.css133
-rw-r--r--templates/index.html.j290
15 files changed, 0 insertions, 1119 deletions
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index d159169..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,339 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
- To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. You must make sure that they, too, receive or can get the
-source code. And you must show them these terms so they know their
-rights.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- <one line to give the program's name and a brief idea of what it does.>
- Copyright (C) <year> <name of author>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 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 General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- <signature of Ty Coon>, 1 April 1989
- Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs. If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
diff --git a/README.md b/README.md
deleted file mode 100644
index b95c89d..0000000
--- a/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# power.eda.gay
-Visualisations of tasmota power monitors
diff --git a/app.py b/app.py
deleted file mode 100644
index 01c49da..0000000
--- a/app.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import database
-import mistune
-import mikrotik
-import devices
-import flask
-import time
-import os
-
-app = flask.Flask(__name__)
-switch = mikrotik.MikroTikSSHDevice()
-markdown_renderer = mistune.create_markdown(
- renderer = mistune.HTMLRenderer(),
- plugins = ["strikethrough", "table", "url"]
-)
-
-@app.route("/")
-def route_index():
- with database.PowerDatabase(host = devices.HOST) as db:
- return flask.render_template(
- "index.html.j2",
- tasmota_devices = [[i[0], markdown_renderer(i[-1])] for i in db.get_tasmota_devices()]
- )
-
-@app.route("/api/mikrotik_devices")
-def api_get_mikrotik_devices():
- return flask.jsonify({i[0]: markdown_renderer(i[1]) for i in switch.interfaces.items()})
-
-@app.route("/api/mikrotik_interface/<interface>")
-def api_poll_mikrotik_interface(interface):
- # time.sleep(0.25)
- try:
- return flask.jsonify(
- {
- "interface": interface,
- "description": switch.interfaces[interface],
- "poe_status": switch.get_interface_poe(interface)
- }
- )
- except (IndexError, KeyError):
- return flask.abort(400)
-
-@app.route("/api/mikrotik_plug")
-def api_get_mikrotik_plug():
- return flask.jsonify({"parent": os.environ["MIKROTIK_TASMOTA"]})
-
-@app.route("/api/plugs")
-def api_poll_plugs():
- with database.PowerDatabase(host = devices.HOST) as db:
- return flask.jsonify(db.get_last_plug_readings())
-
-@app.route("/api/daily_chart")
-def api_get_watt_chart():
- with database.PowerDatabase(host = devices.HOST) as db:
- return flask.jsonify(db.get_watt_chart())
-
-@app.route("/api/longterm_chart")
-def api_get_kwh_chart():
- with database.PowerDatabase(host = devices.HOST) as db:
- return flask.jsonify(db.get_kwh_chart())
-
-if __name__ == "__main__":
- app.run(host = "0.0.0.0", port = int(os.environ["APP_PORT"]), debug = True) \ No newline at end of file
diff --git a/app_requirements.txt b/app_requirements.txt
deleted file mode 100644
index 1a676fc..0000000
--- a/app_requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-flask
-mistune
-fabric
-
diff --git a/cron_Dockerfile b/cron_Dockerfile
deleted file mode 100644
index ed4c9d1..0000000
--- a/cron_Dockerfile
+++ /dev/null
@@ -1,17 +0,0 @@
-FROM ubuntu:latest
-MAINTAINER Eden Attenborough "eda@e.email"
-ENV TZ=Europe/London
-RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
-RUN apt-get update -y
-RUN apt-get install -y python3-pip cron
-RUN mkdir app
-COPY . /app
-WORKDIR /app
-RUN touch .docker
-RUN pip3 install -r cron_requirements.txt
-
-RUN echo "*/1 * * * * root python3 /app/devices.py nothourly > /proc/1/fd/1 2>/proc/1/fd/2" > /etc/crontab
-RUN echo "*/1 * * * * root ( sleep 30; python3 /app/devices.py nothourly > /proc/1/fd/1 2>/proc/1/fd/2 )" >> /etc/crontab
-RUN echo "@daily root python3 /app/devices.py daily > /proc/1/fd/1 2>/proc/1/fd/2" >> /etc/crontab
-ENTRYPOINT ["bash"]
-CMD ["entrypoint.sh"] \ No newline at end of file
diff --git a/cron_requirements.txt b/cron_requirements.txt
deleted file mode 100644
index 2c2bbc9..0000000
--- a/cron_requirements.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-tasmotadevicecontroller==0.0.8
-aiohttp==3.8.3
-pymysql
-python-dotenv
-pyserial
-
diff --git a/database.py b/database.py
deleted file mode 100644
index 4782087..0000000
--- a/database.py
+++ /dev/null
@@ -1,168 +0,0 @@
-from dataclasses import dataclass
-import pymysql
-import os
-
-@dataclass
-class PowerDatabase:
- host: str = None
- user: str = "root"
- passwd: str = None
- db: str = "power"
- port: int = 3306
-
- def __enter__(self):
- if self.passwd is None:
- self.passwd = os.environ["MYSQL_ROOT_PASSWORD"]
-
- if self.host is None:
- self.host = os.environ["MYSQL_HOST"]
-
- try:
- self.__connection = self.__get_connection()
- except Exception as e:
- print(e)
- if e.args[0] == 1049:
- self.__connection = self.__build_db()
- elif e.args[0] == 2003:
- raise ConnectionError(e.args[1])
-
- with self.__connection.cursor() as cursor:
- if "TASMOTA_DEVICES" in os.environ.keys():
- for host, username, password, description in self.get_tasmota_devices():
- cursor.execute("""
- INSERT INTO tasmota_devices (host, username, password)
- VALUES (%s, %s, %s)
- ON DUPLICATE KEY UPDATE username = %s, password = %s;
- """, (host, username, password, username, password))
-
- return self
-
- def __exit__(self, type, value, traceback):
- self.__connection.commit()
- self.__connection.close()
-
- def __get_connection(self):
- return pymysql.connect(
- host = self.host,
- port = self.port,
- user = self.user,
- passwd = self.passwd,
- charset = "utf8mb4",
- database = self.db
- )
-
- def __build_db(self):
- print("Building database...")
- self.__connection = pymysql.connect(
- host = self.host,
- port = self.port,
- user = self.user,
- passwd = self.passwd,
- charset = "utf8mb4",
- )
- with self.__connection.cursor() as cursor:
- # unsafe:
- cursor.execute("CREATE DATABASE %s" % self.db)
- cursor.execute("USE %s" % self.db)
-
- cursor.execute("""
- CREATE TABLE tasmota_devices (
- host VARCHAR(25) NOT NULL PRIMARY KEY,
- username VARCHAR(50) NOT NULL,
- password VARCHAR(50) NOT NULL
- );
- """)
-
- cursor.execute("""
- CREATE TABLE watt_readings (
- host VARCHAR(25) NOT NULL,
- `datetime` DATETIME DEFAULT NOW(),
- reading FLOAT(24) NOT NULL,
- FOREIGN KEY (host) REFERENCES tasmota_devices (host),
- PRIMARY KEY (host, `datetime`)
- );
- """)
-
- cursor.execute("""
- CREATE TABLE kwh_readings (
- host VARCHAR(25) NOT NULL,
- `datetime` DATETIME DEFAULT NOW(),
- reading FLOAT(24) NOT NULL,
- FOREIGN KEY (host) REFERENCES tasmota_devices (host),
- PRIMARY KEY (host, `datetime`)
- );
- """)
-
- self.__connection.commit()
- return self.__connection
-
- def get_tasmota_devices(self):
- o = []
- for d in os.environ["TASMOTA_DEVICES"].split(";"):
- line = d.split(",")
- o.append(line)
- return o
-
- def append_watt_readings(self, host, reading):
- with self.__connection.cursor() as cursor:
- cursor.execute("DELETE FROM watt_readings WHERE `datetime` < DATE_SUB(NOW(), INTERVAL 1 DAY);")
- cursor.execute("INSERT INTO watt_readings (host, reading) VALUES (%s, %s);", (host, reading))
-
- def append_kwh_readings(self, host, reading):
- with self.__connection.cursor() as cursor:
- cursor.execute("INSERT INTO kwh_readings (host, reading) VALUES (%s, %s);", (host, reading))
-
- def get_last_plug_readings(self):
- plugs = [i[0] for i in self.get_tasmota_devices()]
- with self.__connection.cursor() as cursor:
- cursor.execute("SELECT host, MAX(datetime) FROM watt_readings WHERE host IN %s GROUP BY host;", (plugs, ))
- wattplugtimes = cursor.fetchall()
-
- cursor.execute("SELECT host, MAX(datetime) FROM kwh_readings WHERE host IN %s GROUP BY host;", (plugs, ))
- kwhplugtimes = {i[0]: i[1] for i in cursor.fetchall()}
-
- readings = {}
- for host, datetime in wattplugtimes:
- cursor.execute("SELECT host, datetime, reading FROM watt_readings WHERE host = %s AND datetime = %s;", (host, datetime))
- o1 = cursor.fetchone()
- readings[host] = {"watts": (o1[1], o1[2])}
-
- cursor.execute("SELECT host, datetime, reading FROM kwh_readings WHERE host = %s AND datetime = %s;", (host, kwhplugtimes[host]))
- o2 = cursor.fetchone()
- readings[host]["kWh"] = (o2[1], o2[2])
- return readings
-
- def get_watt_chart(self):
- with self.__connection.cursor() as cursor:
- cursor.execute("SELECT DISTINCT host FROM watt_readings;")
- hosts = [i[0] for i in cursor.fetchall()]
-
- out = {}
- for host in hosts:
- cursor.execute("SELECT datetime, reading FROM watt_readings WHERE host = %s ORDER BY datetime;", (host, ))
- out[host] = cursor.fetchall()
-
- return out
-
- def get_kwh_chart(self):
- with self.__connection.cursor() as cursor:
- cursor.execute("SELECT DISTINCT host FROM kwh_readings;")
- hosts = [i[0] for i in cursor.fetchall()]
-
- out = {}
- for host in hosts:
- cursor.execute("SELECT datetime, reading FROM kwh_readings WHERE host = %s ORDER BY datetime;", (host, ))
- out[host] = cursor.fetchall()
-
- return out
-
-def to_series(timeseriesforeach):
- print(timeseriesforeach)
-
-if __name__ == "__main__":
- if not os.path.exists(".docker"):
- import dotenv
- dotenv.load_dotenv(dotenv_path = "power.env")
-
- with PowerDatabase() as db:
- print(db.get_last_plug_readings())
diff --git a/devices.py b/devices.py
deleted file mode 100644
index 8ec9261..0000000
--- a/devices.py
+++ /dev/null
@@ -1,75 +0,0 @@
-import tasmotadevicecontroller
-import database
-import asyncio
-import datetime
-import json
-import sys
-import os
-
-if not os.path.exists(os.path.join("/app", ".docker")):
- import dotenv
- dotenv.load_dotenv(dotenv_path = "power.env")
-HOST = None
-
-async def get_energy_for(host, username = None, password = None):
- device = await tasmotadevicecontroller.TasmotaDevice().connect(host, username, password)
- energy = await device.sendRawRequest("Status 8")
- power = await device.getPower()
- # friendlyname = await device.getFriendlyName()
- return energy["StatusSNS"]["ENERGY"]
- # return {"%s_%s" % (status["FriendlyName"], k): v for k, v in status.items()}
-
-async def poll_watt_for(db: database.PowerDatabase, host, username, password):
- power = await get_energy_for(host, username, password)
- power = float(power['Power'])
- db.append_watt_readings(host, power)
- print("'%s' is using %.1fW at %s" % (host, power, datetime.datetime.now()))
-
-async def poll_yesterday_kwh_for(db: database.PowerDatabase, host, username, password):
- power = await get_energy_for(host, username, password)
- power = float(power['Yesterday'])
- db.append_kwh_readings(host, power)
- print("'%s' used %.1fkWh yesterday" % (host, power))
-
-
-def poll_watt_all():
- loop = asyncio.new_event_loop()
- asyncio.set_event_loop(loop)
- with database.PowerDatabase(host = HOST) as db:
- devices = db.get_tasmota_devices()
- print("There are devices: ", [i[0] for i in devices])
- for host, username, password, description in devices:
- while True:
- try:
- asyncio.run(poll_watt_for(db, host, username, password))
- except:
- print("Retrying %s..." % host)
- continue
- break
-
-
-def poll_kwh_all():
- loop = asyncio.new_event_loop()
- asyncio.set_event_loop(loop)
- with database.PowerDatabase() as db:
- for host, username, password, description in db.get_tasmota_devices():
- while True:
- try:
- asyncio.run(poll_yesterday_kwh_for(db, host, username, password))
- except ConnectionError:
- print("Retrying %s..." % host)
- continue
- break
-
-if __name__ == "__main__":
- while True:
- try:
- if sys.argv[1] == "daily":
- poll_kwh_all()
- else:
- poll_watt_all()
- except ConnectionError as e:
- print("Couldn't connect: ", e, " retrying...")
- continue
- break
-
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index e8a3288..0000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-version: '3'
-
-services:
- cron:
- build:
- context: .
- dockerfile: cron_Dockerfile
- image: jwansek/power
- env_file:
- - ./power.env
- restart: always
- network_mode: host
-
- db:
- image: mariadb
- restart: always
- volumes:
- - logger_data:/var/lib/mysql
- env_file:
- - ./db.env
- network_mode: host
-
-volumes:
- logger_data:
diff --git a/entrypoint.sh b/entrypoint.sh
deleted file mode 100644
index 610bf84..0000000
--- a/entrypoint.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-# https://stackoverflow.com/questions/27771781/how-can-i-access-docker-set-environment-variables-from-a-cron-job/35088810#35088810
-printenv | grep -v "no_proxy" >> /etc/environment
-
-cron -f \ No newline at end of file
diff --git a/mikrotik.py b/mikrotik.py
deleted file mode 100644
index 42a6eeb..0000000
--- a/mikrotik.py
+++ /dev/null
@@ -1,99 +0,0 @@
-from dataclasses import dataclass
-import threading
-import fabric
-import os
-import re
-
-@dataclass
-class MikroTikSSHDevice:
-
- def __post_init__(self):
- self.interfaces = {}
- for i in os.environ["MIKROTIK_INTERFACES"].split(";"):
- self.interfaces.__setitem__(*i.split(","))
- self.is_being_polled = threading.Event()
- self.interface_groups_cache = {}
-
- self.interface_groups = []
- temp = []
- for i, interface_name in enumerate(self.interfaces.keys(), 1):
- temp.append(interface_name)
- if i % 4 == 0:
- self.interface_groups.append(tuple(temp))
- temp = []
-
- # make sure we have some cache
- # also use as sanity-test
- for interface_group in self.interface_groups:
- self._poll_interface_group(interface_group)
-
- def _get_conn(self):
- return fabric.Connection(
- user = os.environ["MIKROTIK_USER"],
- host = os.environ["MIKROTIK_DEVICE"],
- connect_kwargs = {"key_filename": os.environ["MIKROTIK_KEY_PATH"]}
- )
-
- def _get_interfacegroup_containing(self, interface_name):
- for interface_group in self.interface_groups:
- if interface_name in interface_group:
- return interface_group
-
- def _poll_interface_group(self, interface_group):
- self.is_being_polled.set()
- result = self._get_conn().run("/interface/ethernet/poe/monitor %s once" % ",".join(interface_group), hide = True)
- self.is_being_polled.clear()
- parsed_result = self._parse_result(result)
- self.interface_groups_cache[interface_group] = parsed_result
- # print("Cached group:", interface_group)
- return parsed_result
-
- def _parse_result(self, result):
- r = result.stdout
- # print(r)
- s = [re.split(r" +", row.rstrip())[1:] for row in r.split("\r\n")][:-2]
- out = {i: {} for i in s[0][1:]}
- off_interfaces = set()
- for row in s[1:]:
- column_decrimator = 0
- output_name = row[0][:-1]
- # print(output_name)
-
- for i, interface_name in enumerate(out.keys(), 0):
- # print("off_interfaces:", off_interfaces)
- # print(i, interface_name, row[1:][i])
- if interface_name in off_interfaces:
- # print("Skipping '%s' for %s..." % (output_name, interface_name))
- column_decrimator += 1
- else:
- out[interface_name][output_name] = row[1:][i - column_decrimator]
-
- if output_name == "poe-out-status":
- if row[1:][i] != "powered-on":
- # print("Adding %s to off interfaces" % interface_name)
- off_interfaces.add(interface_name)
- return out
-
- # i refuse to use async programming
- def get_interface_poe(self, interface_name):
- interface_group = self._get_interfacegroup_containing(interface_name)
- if self.is_being_polled.is_set():
- result = self.interface_groups_cache[interface_group][interface_name]
- result["cached"] = True
- else:
- result = self._poll_interface_group(interface_group)[interface_name]
- result["cached"] = False
-
- return result
-
-if __name__ == "__main__":
- if not os.path.exists(os.path.join("/app", ".docker")):
- import dotenv
- dotenv.load_dotenv(dotenv_path = "power.env")
-
- import time
- mikrotik = MikroTikSSHDevice()
- print("Ready.")
- for interface_name in mikrotik.interfaces.keys():
- threading.Thread(target = lambda i: print(i, mikrotik.get_interface_poe(i)), args = (interface_name, )).start()
- time.sleep(1) \ No newline at end of file
diff --git a/power.env.example b/power.env.example
deleted file mode 100644
index 1b0b3f0..0000000
--- a/power.env.example
+++ /dev/null
@@ -1,13 +0,0 @@
-MYSQL_ROOT_PASSWORD=*******************
-MYSQL_HOST=192.168.69.3
-# strings cannot contain , ; or #
-TASMOTA_DEVICES=switch.plug,admin,*******************,[Mikrotik CRS112-8P-4S-IN](https://wiki.eda.gay/index.php/Switches);nas.plug,admin,*******************,[TrueNAS NAS](https://wiki.eda.gay/index.php/TrueNAS_NAS);12vbrick.plug,admin,*******************,KVM Switch & Backup External Hard Drive & etc;backup.plug,admin,*******************,Backup NAS
-
-MIKROTIK_DEVICE=/dev/ttyUSB0
-MIKROTIK_BAUD=115200
-MIKROTIK_USER=admin
-MIKROTIK_PASS=*******************
-MIKROTIK_INTERFACES=ether1,[EAP225 Wi-Fi AP](https://wiki.eda.gay/index.php/Main_Page);ether2,[TL-RP108GE](https://wiki.eda.gay/index.php/Switches) & [pfsense router](https://wiki.eda.gay/index.php/Pfsense_router);ether3,[Mikrotik CSS610-8G-2S+IN](https://wiki.eda.gay/index.php/Switches);ether4,[PiKVM](https://wiki.eda.gay/index.php/Blikvm_PiKVM);ether5,interface5;ether6,[Intel Compute Stick](https://wiki.eda.gay/index.php/Intel_Compute_Stick);ether7,SSH/Git/PiHole Raspberry Pi;ether8,interface8
-MIKROTIK_TASMOTA=switch.plug
-
-APP_PORT = 5021
diff --git a/static/scripts.js b/static/scripts.js
deleted file mode 100644
index 10facc4..0000000
--- a/static/scripts.js
+++ /dev/null
@@ -1,83 +0,0 @@
-$(document).ready(function() {
- fetch("/api/mikrotik_plug").then((resp) => {
- resp.json().then((body) => {
- const MIKROTIK_PARENT = body["parent"];
-
- const parent_elem = document.getElementById("tr_" + MIKROTIK_PARENT);
-
- fetch("/api/mikrotik_devices").then((resp) => {
- resp.json().then((body) => {
- Object.keys(body).reverse().forEach((interface, i) => {
- let tr_elem = document.createElement("tr");
- tr_elem.classList.add("mikrotik_tr")
- tr_elem.id = "mikrotik_tr_" + interface;
- // console.log(interface, body[interface]);
- parent_elem.parentNode.insertBefore(tr_elem, parent_elem.nextSibling);
-
- for (let i = 0; i <= 4; i++) {
- let td_elem = document.createElement("td");
- if (i === 0) {
- td_elem.innerHTML = interface;
- td_elem.classList.add("mikrotik_interface_name")
- } else if (i === 1) {
- td_elem.innerHTML = body[interface];
- } else if (i === 2) {
- td_elem.id = "mikrotik_td_" + interface + "_watts_now";
- } else if (i === 3) {
- td_elem.id = "mikrotik_td_" + interface + "_watts_yesterday";
- }
- tr_elem.appendChild(td_elem);
- }
- });
-
- get_mikrotik_table(Object.keys(body));
- });
- });
- });
- });
-
- get_main_table();
-})
-
-function get_mikrotik_table(interfaces) {
- interfaces.forEach((interface) => {
- fetch("/api/mikrotik_interface/" + interface).then((resp) => {
- resp.json().then((body) => {
- console.log(body["poe_status"]);
- // TODO: Add a delay if it was cached
- });
- });
- });
-
- setTimeout(function() {get_mikrotik_table(interfaces);}, 1000)
-}
-
-function get_main_table() {
- fetch("/api/plugs").then((resp) => {
- resp.json().then((body) => {
- let watts_sum = 0;
- let kwh_sum = 0;
- Object.keys(body).forEach((host, i) => {
- watts = body[host]["watts"];
- kwh = body[host]["kWh"];
- document.getElementById(host + "_watts_now").innerHTML = watts[1];
- document.getElementById(host + "_watts_yesterday").innerHTML = kwh[1];
- watts_sum += watts[1];
- kwh_sum += kwh[1];
-
- document.getElementById("watts_last_updated").innerHTML = "Current power usage last updated at " + watts[0];
- document.getElementById("kwh_last_updated").innerHTML = "Yesterday's power usage last updated at " + kwh[0];
-
- console.log(host, watts[0], watts[1], kwh[1])
- });
- document.getElementById("sum_watts_now").innerHTML = watts_sum;
- document.getElementById("sum_watts_yesterday").innerHTML = kwh_sum;
- });
- });
-
- setTimeout(get_main_table, 10000);
-}
-
-function sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
-} \ No newline at end of file
diff --git a/static/style.css b/static/style.css
deleted file mode 100644
index 0ab4e82..0000000
--- a/static/style.css
+++ /dev/null
@@ -1,133 +0,0 @@
-body {
- font-family: Helvetica, sans-serif;
-}
-
-header {
- font-size: large;
- padding-left: 1%;
-}
-
-a {
- color: black;
- font-weight: bold;
- padding-top: 1px;
- text-decoration: none;
-}
-
-a:hover {
- text-decoration: underline;
-}
-
-header nav {
- flex-grow: 1;
- display: inline;
-}
-
-#externallinks {
- background-color: black;
- text-align: left;
-}
-
-#externallinks nav ul li a {
- color: #f1f3f3;
- font-size: smaller;
-}
-
-header div {
- padding-left: 20px;
- /* padding-bottom: 10px; */
-}
-
-nav ul {
- margin: 0;
- padding: 0;
-}
-
-nav li {
- display: inline-block;
- list-style-type: none;
-}
-
-nav a {
- text-decoration: none;
- display: block;
- padding: 5px 6px 5px 6px;
- color: black;
-}
-
-footer {
- padding-top: 100px;
- font-size: x-small;
-}
-
-#main_content {
- padding-left: 2.5%;
- padding-right: 2.5;
-}
-
-
-#multicharts ul li {
- list-style-type: none;
- width: 45%;
- display: inline-flex;
- background-color: pink;
- min-height: 350px;
- margin-bottom: 7px;
- overflow: hidden;
- flex-direction: column;
- justify-content: space-between;
- /* border-color: black;
- border-width: 2px;
- border-radius: 1;
- border-style: ridge; */
-}
-
-.chart_container {
- display: flex;
- flex-direction: row-reverse;
-}
-
-#power_table {
- width: 90%;
-}
-
-#header_row {
- background-color: black;
-}
-
-#header_row td {
- color: #f1f3f3;
- font-weight: bold;
-}
-
-#power_table tr {
- margin-bottom: 3px;
-}
-
-#last_updated_ul {
- font-size: small;
-}
-
-#sum_row {
- background-color: gainsboro;
-}
-
-.mikrotik_tr {
- font-size: x-small;
- padding-bottom: 1em;
- padding-top: 1em;
- border-spacing:0 5px;
-}
-
-.mikrotik_interface_name {
- text-align: right;
- padding-right: 15px;
-}
-
-@media screen and (max-width: 1200px) {
- #multicharts ul li {
- width: 100%;
- min-height: 500px;
- height: 100%;
- }
-} \ No newline at end of file
diff --git a/templates/index.html.j2 b/templates/index.html.j2
deleted file mode 100644
index 69fcd31..0000000
--- a/templates/index.html.j2
+++ /dev/null
@@ -1,90 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <title>edaweb power monitor</title>
- <!-- JQuery and JQurty UI current version -->
- <script src='https://code.jquery.com/jquery-3.6.0.js'></script>
- <script src="https://code.jquery.com/ui/1.13.1/jquery-ui.js"></script>
-
- <!-- Highcharts libraries -->
- <script type="text/javascript" src="https://code.highcharts.com/highcharts.js"></script>
- <script type="text/javascript" src="https://code.highcharts.com/highcharts-more.js"></script>
- <script type="text/javascript" src="https://code.highcharts.com/modules/exporting.js"></script>
- <script type="text/javascript" src="https://code.highcharts.com/modules/export-data.js"></script>
- <script type="text/javascript" src="https://code.highcharts.com/modules/accessibility.js"></script>
-
- <script type="text/javascript" src="https://code.highcharts.com/maps/modules/map.js"></script>
- <script type="text/javascript" src="https://code.highcharts.com/mapdata/custom/world-robinson.js"></script>
-
- <!-- local Javascript files -->
- <script type="text/javascript" src="{{ url_for('static', filename='scripts.js') }}"></script>
- <!-- remote and local CSS stylesheet -->
- <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
-</head>
-
-<body>
- <header>
- <h1>edaweb power monitoring</h1>
- <div id="externallinks">
- <nav>
- <ul>
- <li><a href="http://eda.gay">eda.gay</a></li>
- <li><a href="http://wiki.eda.gay">wiki.eda.gay</a></li>
- </ul>
- </nav>
- </div>
- </header>
-
- <div id="main_content">
- <div id="multicharts">
- <ul id="charts_ul">
- <li>
- <figure class="chart_container">
- <div class="minichart" id="longterm_chart"></div>
- </figure>
- </li>
- <li>
- <figure class="chart_container">
- <div class="minichart" id="hourly_chart"></div>
- </figure>
- </li>
- </ul>
- </div>
-
- <table id="power_table">
- <tr id="header_row">
- <td>Plug local hostname</td>
- <td>Plug description</td>
- <td>Current power usage (W)</td>
- <td>Power usage yesterday (kWh)</td>
- </tr>
- {% for host, description_md in tasmota_devices %}
- <tr id="tr_{{ host }}">
- <td><a href="http://{{ host }}" target="_blank">{{ host }}</a></td>
- <td>{{ description_md|safe }}</td>
- <td id="{{ host }}_watts_now"></td>
- <td id="{{ host }}_watts_yesterday"></td>
- </tr>
- {% endfor %}
- <tr id="sum_row">
- <td></td>
- <td></td>
- <td id="sum_watts_now"></td>
- <td id="sum_watts_yesterday"></td>
- </tr>
- </table>
- </div>
-
- <ul id="last_updated_ul">
- <li id="watts_last_updated">Current power usage never updated</li>
- <li id="kwh_last_updated">Yesterday's power usage never updated</li>
- <li id="switch_last_updated">Switch power usage never updated</li>
- </ul>
-
- <footer>
- <p><a href="https://github.com/jwansek/power.eda.gay">Source code released under GPLv3</a></p>
- <p><a href="https://www.fsf.org/campaigns/freejs">Read the Free Software Foundations statements about JavaScript</a></p>
- </footer>
-</body> \ No newline at end of file