diff --git a/.gitignore b/.gitignore index 1f05a21..c80648b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ !ngrok !.github !.github/** -!scripts \ No newline at end of file +!scripts +!etc/ +!etc/** diff --git a/etc/dhcpcd.conf b/etc/dhcpcd.conf new file mode 100644 index 0000000..3486049 --- /dev/null +++ b/etc/dhcpcd.conf @@ -0,0 +1,66 @@ +# A sample configuration for dhcpcd. +# See dhcpcd.conf(5) for details. +# Allow users of this group to interact with dhcpcd via the control socket. +#controlgroup wheel + +# Inform the DHCP server of our hostname for DDNS. +hostname + +# Use the hardware address of the interface for the Client ID. +clientid +# or +# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361. +# Some non-RFC compliant DHCP servers do not reply with this set. +# In this case, comment out duid and enable clientid above. +#duid + +# Persist interface configuration when dhcpcd exits. +persistent + +# Rapid commit support. +# Safe to enable by default because it requires the equivalent option set +# on the server to actually work. +option rapid_commit + +# A list of options to request from the DHCP server. +option domain_name_servers, domain_name, domain_search, host_name +option classless_static_routes +# Respect the network MTU. This is applied to DHCP routes. +option interface_mtu + +# Most distributions have NTP support. +#option ntp_servers + +# A ServerID is required by RFC2131. +require dhcp_server_identifier + +# Generate SLAAC address using the Hardware Address of the interface +#slaac hwaddr +# OR generate Stable Private IPv6 Addresses based from the DUID +slaac private + +# Example static IP configuration: +#interface eth0 +#static ip_address=192.168.0.10/24 +#static ip6_address=fd51:42f8:caae:d92e::ff/64 +#static routers=192.168.0.1 +#static domain_name_servers=192.168.0.1 8.8.8.8 fd51:42f8:caae:d92e::1 + +# It is possible to fall back to a static IP if DHCP fails: +# define static profile +#profile static_eth0 +#static ip_address=192.168.1.23/24 +#static routers=192.168.1.1 +#static domain_name_servers=192.168.1.1 + +# fallback to static profile on eth0 +#interface eth0 +#fallback static_eth0 +inteface wlan0 + static ip_address=192.168.5.1/24 + nohook wpa_supplicant + +interface eth0 + static ip_address=192.168.8.106/24 + static routers=192.168.8.1 + static domain_name_server=8.8.8.8 diff --git a/etc/dnsmasq.conf b/etc/dnsmasq.conf new file mode 100644 index 0000000..b6e5adb --- /dev/null +++ b/etc/dnsmasq.conf @@ -0,0 +1,4 @@ +interface=wlan0 +dhcp-range=192.168.5.2,192.168.5.20,255.255.255.0,24h +domain=wlan +address=/gw.wlan/192.168.5.1 diff --git a/openoceancamera/Camera.py b/openoceancamera/Camera.py deleted file mode 100755 index a2e6ccc..0000000 --- a/openoceancamera/Camera.py +++ /dev/null @@ -1,81 +0,0 @@ -try: - from picamera import PiCamera - from time import sleep,time - from datetime import datetime -except: - print( - "Program is not running on a Raspberry Pi. The camera module cannot be loaded" - ) - exit(0) - - -class Camera(object): - def __init__(self, resolution=(1920, 1080), shutter_speed=5000, iso=0, frequency=3): - self.camera = PiCamera() - self.set_camera_resolution(resolution) - self.set_shutter_speed(shutter_speed) - self.set_iso(iso) - self.set_capture_frequency(frequency) - - def do_capture(self, filename="test.jpg", continuous=False, slot=None): - if continuous: - #try: - # for f in self.camera.capture_continuous('/media/pi/OPENOCEANCA/img{timestamp:%Y-%m-%d-%H-%M-%S}.jpg'): - # sleep((self.frequency-1)) - # print (f) - # currenttime=datetime.now() - # if currenttime= datetime.now(): + future_slots.append(slot) + # Sorts the slots in case they may not be + future_slots = sorted(future_slots, key=lambda x: x["start"]) + if len(future_slots): + return future_slots[0] + else: + return None + def time_to_nearest_schedule(self): - self.update_current_time() possible_slots = [] # Get the future slots for slot in self.schedule_data: - if slot["start"] >= self.time_now: + if slot["start"] >= datetime.now(): possible_slots.append(slot) # Sorts the slots in case they may not be possible_slots = sorted(possible_slots, key=lambda x: x["start"]) - print(f"The time now is: {self.time_now}") + print(f"The time now is: {datetime.now()}") print(f"The future slots are: {possible_slots}") # Take the difference between the most recent slot's start time and time now - delta = possible_slots[0]["start"] - self.time_now + delta = possible_slots[0]["start"] - datetime.now() print(f"The time difference is: {delta}") # Returns an integer value for the time difference in seconds return int(delta.total_seconds()) - - def next_future_timeslot(self): - self.update_current_time() - - future_slots = [] - # Get the future slots - for slot in self.schedule_data: - if slot["start"] >= self.time_now: - future_slots.append(slot) - # Sorts the slots in case they may not be - future_slots = sorted(future_slots, key=lambda x: x["start"]) - if len(future_slots): - return future_slots[0] - else: - return None diff --git a/openoceancamera/TSL2561/.categories b/openoceancamera/TSL2561/.categories deleted file mode 100755 index e8afca7..0000000 --- a/openoceancamera/TSL2561/.categories +++ /dev/null @@ -1,5 +0,0 @@ -lang:java -lang:python -lang:arduino -lang:C -sku:TSL2561_I2CS diff --git a/openoceancamera/TSL2561/Arduino/TSL2561.ino b/openoceancamera/TSL2561/Arduino/TSL2561.ino deleted file mode 100755 index 3555067..0000000 --- a/openoceancamera/TSL2561/Arduino/TSL2561.ino +++ /dev/null @@ -1,74 +0,0 @@ -// Distributed with a free-will license. -// Use it any way you want, profit or free, provided it fits in the licenses of its associated works. -// TSL2561 -// This code is designed to work with the TSL2561_I2CS I2C Mini Module available from ControlEverything.com. -// https://www.controleverything.com/content/Light?sku=TSL2561_I2CS#tabs-0-product_tabset-2 - -#include - -// TSL2561 I2C address is 0x39(57) -#define Addr 0x39 - -void setup() -{ - // Initialise I2C communication as MASTER - Wire.begin(); - // Initialise serial communication, set baud rate = 9600 - Serial.begin(9600); - - // Starts I2C communication - Wire.beginTransmission(Addr); - // Select control register - Wire.write(0x00 | 0x80); - // Power ON mode - Wire.write(0x03); - // Stop I2C Transmission - Wire.endTransmission(); - - // Starts I2C communication - Wire.beginTransmission(Addr); - // Select timing register - Wire.write(0x01 | 0x80); - // Nominal integration time = 402ms - Wire.write(0x02); - // Stop I2C Transmission - Wire.endTransmission(); - delay(300); - -} - -void loop() -{ - unsigned int data[4]; - for(int i = 0; i < 4; i++) - { - // Starts I2C communication - Wire.beginTransmission(Addr); - // Select data register - Wire.write((140 + i)); - // Stop I2C Transmission - Wire.endTransmission(); - - // Request 1 byte of data - Wire.requestFrom(Addr, 1); - - // Read 1 bytes of data - if(Wire.available() == 1) - { - data[i] = Wire.read(); - } - delay(200); - } - - // Convert the data - double ch0 = ((data[1] & 0xFF) * 256) + (data[0] & 0xFF); - double ch1 = ((data[3] & 0xFF) * 256) + (data[2] & 0xFF); - - // Output data to serial monitor - Serial.print("Full Spectrum(IR + Visible) :"); - Serial.println(ch0); - Serial.print("Infrared Value :"); - Serial.println(ch1); - Serial.print("Visible Value :"); - Serial.println(ch0-ch1); -} diff --git a/openoceancamera/TSL2561/C/TSL2561.c b/openoceancamera/TSL2561/C/TSL2561.c deleted file mode 100755 index 4f50805..0000000 --- a/openoceancamera/TSL2561/C/TSL2561.c +++ /dev/null @@ -1,60 +0,0 @@ -// Distributed with a free-will license. -// Use it any way you want, profit or free, provided it fits in the licenses of its associated works. -// TSL2561 -// This code is designed to work with the TSL2561_I2CS I2C Mini Module available from ControlEverything.com. -// https://www.controleverything.com/content/Light?sku=TSL2561_I2CS#tabs-0-product_tabset-2 - -#include -#include -#include -#include -#include -#include - -void main() -{ - // Create I2C bus - int file; - char *bus = "/dev/i2c-1"; - if((file = open(bus, O_RDWR)) < 0) - { - printf("Failed to open the bus. \n"); - exit(1); - } - // Get I2C device, TSL2561 I2C address is 0x39(57) - ioctl(file, I2C_SLAVE, 0x39); - - // Select control register(0x00 | 0x80) - // Power ON mode(0x03) - char config[2] = {0}; - config[0] = 0x00 | 0x80; - config[1] = 0x03; - write(file, config, 2); - // Select timing register(0x01 | 0x80) - // Nominal integration time = 402ms(0x02) - config[0] = 0x01 | 0x80; - config[1] = 0x02; - write(file, config, 2); - sleep(1); - - // Read 4 bytes of data from register(0x0C | 0x80) - // ch0 lsb, ch0 msb, ch1 lsb, ch1 msb - char reg[1] = {0x0C | 0x80}; - write(file, reg, 1); - char data[4] = {0}; - if(read(file, data, 4) != 4) - { - printf("Erorr : Input/output Erorr \n"); - } - else - { - // Convert the data - float ch0 = (data[1] * 256 + data[0]); - float ch1 = (data[3] * 256 + data[2]); - - // Output data to screen - printf("Full Spectrum(IR + Visible) : %.2f lux \n", ch0); - printf("Infrared Value : %.2f lux \n", ch1); - printf("Visible Value : %.2f lux \n", (ch0 - ch1)); - } -} diff --git a/openoceancamera/TSL2561/Java/TSL2561.java b/openoceancamera/TSL2561/Java/TSL2561.java deleted file mode 100755 index 04235f3..0000000 --- a/openoceancamera/TSL2561/Java/TSL2561.java +++ /dev/null @@ -1,46 +0,0 @@ -// Distributed with a free-will license. -// Use it any way you want, profit or free, provided it fits in the licenses of its associated works. -// TSL2561 -// This code is designed to work with the TSL2561_I2CS I2C Mini Module available from ControlEverything.com. -// https://www.controleverything.com/content/Light?sku=TSL2561_I2CS#tabs-0-product_tabset-2 - -import com.pi4j.io.i2c.I2CBus; -import com.pi4j.io.i2c.I2CDevice; -import com.pi4j.io.i2c.I2CFactory; -import java.io.IOException; -import java.text.DecimalFormat; -import java.text.NumberFormat; - -public class TSL2561 -{ - public static void main(String args[]) throws Exception - { - // Create I2C bus - I2CBus Bus = I2CFactory.getInstance(I2CBus.BUS_1); - // Get I2C device, TSL2561 I2C address is 0x39(57) - I2CDevice device = Bus.getDevice(0x39); - - // Select control register - // Power ON mode - device.write(0x00 | 0x80, (byte)0x03); - // Select timing register - // Nominal integration time = 402ms - device.write(0x01 | 0x80, (byte)0x02); - Thread.sleep(500); - - // Read 4 bytes of data - // ch0 lsb, ch0 msb, ch1 lsb, ch1 msb - byte[] data=new byte[4]; - device.read(0x0C | 0x80, data, 0, 4); - - // Convert the data - double ch0 = ((data[1] & 0xFF)* 256 + (data[0] & 0xFF)); - double ch1 = ((data[3] & 0xFF)* 256 + (data[2] & 0xFF)); - - // Output data to screen - System.out.printf("Full Spectrum(IR + Visible) : %.2f lux %n", ch0); - System.out.printf("Infrared Value : %.2f lux %n", ch1); - System.out.printf("Visible Value : %.2f lux %n", (ch0 - ch1)); - } -} - \ No newline at end of file diff --git a/openoceancamera/TSL2561/Onion Omega Python/TSL2561.py b/openoceancamera/TSL2561/Onion Omega Python/TSL2561.py deleted file mode 100755 index 6f002d6..0000000 --- a/openoceancamera/TSL2561/Onion Omega Python/TSL2561.py +++ /dev/null @@ -1,39 +0,0 @@ -# Distributed with a free-will license. -# Use it any way you want, profit or free, provided it fits in the licenses of its associated works. -# TSL2561 -# This code is designed to work with the TSL2561_I2CS I2C Mini Module available from ControlEverything.com. -# https://www.controleverything.com/content/Light?sku=TSL2561_I2CS#tabs-0-product_tabset-2 - -from OmegaExpansion import onionI2C -import time - -# Get I2C bus -i2c = onionI2C.OnionI2C() - -# TSL2561 address, 0x39(57) -# Select control register, 0x00(00) with command register, 0x80(128) -# 0x03(03) Power ON mode -i2c.writeByte(0x39, 0x00 | 0x80, 0x03) -# TSL2561 address, 0x39(57) -# Select timing register, 0x01(01) with command register, 0x80(128) -# 0x02(02) Nominal integration time = 402ms -i2c.writeByte(0x39, 0x01 | 0x80, 0x02) - -time.sleep(0.5) - -# Read data back from 0x0C(12) with command register, 0x80(128), 2 bytes -# ch0 LSB, ch0 MSB -data = i2c.readBytes(0x39, 0x0C | 0x80, 2) - -# Read data back from 0x0E(14) with command register, 0x80(128), 2 bytes -# ch1 LSB, ch1 MSB -data1 = i2c.readBytes(0x39, 0x0E | 0x80, 2) - -# Convert the data -ch0 = data[1] * 256 + data[0] -ch1 = data1[1] * 256 + data1[0] - -# Output data to screen -print "Full Spectrum(IR + Visible) :%d lux" %ch0 -print "Infrared Value :%d lux" %ch1 -print "Visible Value :%d lux" %(ch0 - ch1) diff --git a/openoceancamera/TSL2561/Python/TSL2561.py b/openoceancamera/TSL2561/Python/TSL2561.py deleted file mode 100755 index 9f82d9d..0000000 --- a/openoceancamera/TSL2561/Python/TSL2561.py +++ /dev/null @@ -1,43 +0,0 @@ -# Distributed with a free-will license. -# Use it any way you want, profit or free, provided it fits in the licenses of its associated works. -# TSL2561 -# This code is designed to work with the TSL2561_I2CS I2C Mini Module available from ControlEverything.com. -# https://www.controleverything.com/content/Light?sku=TSL2561_I2CS#tabs-0-product_tabset-2 - -import smbus -import time - -# Get I2C bus -bus = smbus.SMBus(1) -try: - # TSL2561 address, 0x39(57) - # Select control register, 0x00(00) with command register, 0x80(128) - # 0x03(03) Power ON mode - bus.write_byte_data(0x39, 0x00 | 0x80, 0x03) - # TSL2561 address, 0x39(57) - # Select timing register, 0x01(01) with command register, 0x80(128) - # 0x02(02) Nominal integration time = 402ms - bus.write_byte_data(0x39, 0x01 | 0x80, 0x02) - - time.sleep(0.5) - - # Read data back from 0x0C(12) with command register, 0x80(128), 2 bytes - # ch0 LSB, ch0 MSB - data = bus.read_i2c_block_data(0x39, 0x0C | 0x80, 2) - - # Read data back from 0x0E(14) with command register, 0x80(128), 2 bytes - # ch1 LSB, ch1 MSB - data1 = bus.read_i2c_block_data(0x39, 0x0E | 0x80, 2) - - # Convert the data - ch0 = data[1] * 256 + data[0] - ch1 = data1[1] * 256 + data1[0] - - # Output data to screen - #print "Full Spectrum(IR + Visible) :%d lux" %ch0 - print ch1 - #print "Infrared Value :%d lux" %ch1 - #print "Visible Value :%d lux" %(ch0 - ch1) - -except: - print "TSL2561 not connected" diff --git a/openoceancamera/TSL2561/README.md b/openoceancamera/TSL2561/README.md deleted file mode 100755 index b91645c..0000000 --- a/openoceancamera/TSL2561/README.md +++ /dev/null @@ -1,84 +0,0 @@ -[![TSL2561](TSL2561_I2CS.png)](https://www.controleverything.com/content/Light?sku=TSL2561_I2CS) -# TSL2561 -TSL2561 Light-to-Digital Converter - -The TSL2561 is a light-to-digital converter that transforms light intensity to a digital signal output. - -This Device is available from ControlEverything.com [SKU: TSL2561_I2CS] - -https://www.controleverything.com/content/Light?sku=TSL2561_I2CS - -This Sample code can be used with Raspberry Pi, Arduino, Beaglebone Black and Onion Omega. - -## Java -Download and install pi4j library on Raspberry pi. Steps to install pi4j are provided at: - -http://pi4j.com/install.html - -Download (or git pull) the code in pi. - -Compile the java program. -```cpp -$> pi4j TSL2561.java -``` - -Run the java program. -```cpp -$> pi4j TSL2561 -``` - -## Python -Download and install smbus library on Raspberry pi. Steps to install smbus are provided at: - -https://pypi.python.org/pypi/smbus-cffi/0.5.1 - -Download (or git pull) the code in pi. Run the program. - -```cpp -$> python TSL2561.py -``` - -## Arduino -Download and install Arduino Software (IDE) on your machine. Steps to install Arduino are provided at: - -https://www.arduino.cc/en/Main/Software - -Download (or git pull) the code and double click the file to run the program. - -Compile and upload the code on Arduino IDE and see the output on Serial Monitor. - - -## C - -Download (or git pull) the code in Beaglebone Black. - -Compile the c program. -```cpp -$>gcc TSL2561.c -o TSL2561 -``` -Run the c program. -```cpp -$>./TSL2561 -``` - -## Onion Omega - -Get Started and setting up the Onion Omega according to steps provided at : - -https://wiki.onion.io/Get-Started - -To install the Python module, run the following commands: -```cpp -opkg update -``` -```cpp -opkg install python-light pyOnionI2C -``` - -Download (or git pull) the code in Onion Omega. Run the program. - -```cpp -$> python TSL2561.py -``` - -#####The code output is the lux value of ambient light. diff --git a/openoceancamera/TSL2561/TSL2561_I2CS.png b/openoceancamera/TSL2561/TSL2561_I2CS.png deleted file mode 100755 index bcfe5ad..0000000 Binary files a/openoceancamera/TSL2561/TSL2561_I2CS.png and /dev/null differ diff --git a/openoceancamera/StreamingOutput.py b/openoceancamera/appserver/StreamingOutput.py similarity index 100% rename from openoceancamera/StreamingOutput.py rename to openoceancamera/appserver/StreamingOutput.py diff --git a/openoceancamera/appserver/__init__.py b/openoceancamera/appserver/__init__.py new file mode 100644 index 0000000..198fea2 --- /dev/null +++ b/openoceancamera/appserver/__init__.py @@ -0,0 +1,296 @@ +import os +from os import path +from flask import Flask, request, send_file, jsonify, Response +from flask_cors import CORS +from flask_socketio import SocketIO, send, emit +from picamera import PiCamera +import threading +from time import sleep +import json +import base64 +from datetime import datetime +from uuid import uuid1 + +from .StreamingOutput import StreamingOutput +from constants import EXTERNAL_DRIVE +from sensors import Sensor +from subsealight import PWM +from logger import logger +from restart import restart_code +from camera.utils import get_camera_name +from uploader import DropboxUploader + +app = Flask("OpenOceanCam") +app.config["CORS_HEADERS"] = "Content-Type" + +socketio = SocketIO(app, cors_allowed_origins="*", ping_timeout=5, ping_interval=5) + +@app.route("/setCameraName", methods=["POST"]) +def set_camera_name(): + if request.method == 'POST': + camera_name = request.get_json()["name"] + with open("/home/pi/openoceancamera/camera_name.txt", "w") as camera_name_file: + camera_name_file.write(camera_name) + return camera_name + +@app.route("/syncTime", methods=["GET", "POST"]) +def sync_time(): + if request.method == "POST": + try: + data = request.get_json() + date_input = data["date"] + timezone = data["timezone"] + clear_cmd = ('sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 10 6') + os.system(clear_cmd) + os.system(f"sudo timedatectl set-timezone {timezone}") + os.system(f"sudo date -s '{date_input}'") + # Save the system time to RTC - + os.system("sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 1") + os.system("sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 2") + threading.Thread(target=restart_code).start() + return "OK", 200 + except Exception as err: + return str(err), 400 + +@app.route("/clearSchedule", methods=["GET"]) +def clearSchedule(): + try: + with open("/home/pi/openoceancamera/schedule.json", "w") as outfile: + json.dump(json.loads("[]"), outfile) + clear_cmd = ('sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 10 6') + os.system(clear_cmd) + threading.Thread(target=restart_code).start() + return "OK", 200 + except Exception as err: + return str(err), 400 + +@app.route("/setSchedule", methods=["POST", "GET"]) +def set_schedule(): + if request.method == "POST": + print(request.get_json()) + camera_config = request.get_json() + with open("/home/pi/openoceancamera/schedule.json", "w") as outfile: + json.dump(camera_config, outfile) + date_input = camera_config[0]["date"] + timezone = camera_config[0]["timezone"] + clear_cmd = ('sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 10 6') # convert to python function calls for wittyPi + os.system(clear_cmd) + os.system(f"sudo timedatectl set-timezone {timezone}") + print(timezone) + print(date_input) + # Sets the system time to the user's phone time + os.system(f"sudo date -s '{date_input}'") + # Save the system time to RTC - + os.system("sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 1") + os.system("sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 2") + pathv = path.exists(EXTERNAL_DRIVE) + threading.Thread(target=restart_code).start() + if pathv: + return "OK", 200 + else: + logger.error("Error: USB storage device not mounted") + return "Error: USB storage device not mounted", 400 + +@app.route("/viewConfig", methods=["GET"]) +def returnConfig(): + if request.method == "GET": + try: + with open("/home/pi/openoceancamera/schedule.json", "r") as camera_config_file: + camera_config = json.loads(camera_config_file.read()) + response = { + "local_time": datetime.now().strftime("%d-%B-%Y %H:%M:%S"), + "local_timezone": str(datetime.utcnow().astimezone().tzinfo) , + "config": json.dumps(camera_config), + "camera_name": get_camera_name() + } + return response, 200 + except Exception as err: + return str(err), 400 + +@app.route("/getLogs", methods=["GET"]) +def getLogs(): + if request.method == "GET": + try: + with open("/home/pi/system_logs.txt", 'r') as f: + data = f.read() + return data + except Exception as err: + logger.error(err) + return str(err), 400 + +@app.route("/clearLogs", methods=["GET"]) +def clearLogs(): + if request.method == "GET": + try: + open("/home/pi/system_logs.txt", 'w').close() + return "OK", 200 + except Exception as err: + return str(err), 400 + +@app.route("/testPhoto", methods=["POST", "GET"]) +def sendTestPic(): + if request.method == "POST": + try: + data = request.get_json(force=True) + sensor = Sensor() + print(data) + PWM.switch_on(data[0]["light"]) + iso = data[0].get("iso", "auto") + shutter_speed = data[0].get("shutter_speed", "auto") + exposure_mode = data[0].get("exposure_mode", "auto") + exposure_compensation = data[0].get("exposure_compensation", 0) + slot_resolution = data[0].get("resolution", {"x": "1920", "y": "1080"}) + resolution = (int(slot_resolution["x"]), int(slot_resolution["y"])) + with PiCamera(resolution=resolution) as camera: + camera.iso = iso + camera.shutter_speed = shutter_speed + camera.exposure_mode = exposure_mode + camera.exposure_compensation = exposure_compensation + camera.capture("/home/pi/openoceancamera/test.jpg") + with open("/home/pi/openoceancamera/test.jpg", "rb") as image: + img_base64 = base64.b64encode(image.read()) + sensor.read_sensor_data() + sensor_data = sensor.get_sensor_data() + response = { + "image": img_base64.decode("utf-8"), + "sensors": json.dumps(sensor_data), + } + PWM.switch_off() + return jsonify(response), 200 + except Exception as err: + logger.error(err) + PWM.switch_off() + return str(err), 400 + +@app.route("/testPhotoMem", methods=["POST", "GET"]) +def sendTestPicMem(): + if request.method == "POST": + data = request.get_json(force=True) + PWM.switch_on(data[0]["light"]) + flag = "SUCCESS" + pathv = path.exists(EXTERNAL_DRIVE) + sensor = Sensor() + if pathv: + try: + iso = data[0].get("iso", "auto") + shutter_speed = data[0].get("shutter_speed", "auto") + resolution = (1920,1080) + framerate = 30 + try: + with PiCamera() as camera: + camera.iso = iso + camera.shutter_speed = shutter_speed + filename1 = EXTERNAL_DRIVE + "/" + str(uuid1()) + ".jpg" + camera.capture(filename1) + print("Written") + except Exception as err: + return str(err) , 400 + + try: + with PiCamera(resolution=resolution, framerate=framerate) as camera: + filename2 = EXTERNAL_DRIVE + "/" + str(uuid1()) + ".h264" + camera.start_recording(filename2) + print("Started recording") + sleep(3) + camera.stop_recording() + except Exception as err: + return str(err), 400 + except Exception as err: + return str(err), 400 + else: + return "USB storage device with name OOCAM required", 400 + PWM.switch_off() + sensor.read_sensor_data() + sensor_data = sensor.get_sensor_data() + response = { + "sensors": json.dumps(sensor_data), + } + return jsonify(response), 200 + +@app.route("/update", methods=["GET","POST"]) +def update_code(): + if request.method == "POST": + try: + data = request.get_json() + ssid = data["ssid"] + psk = data["psk"] + os.system(f"sudo sh /home/pi/connect_to_wifi.sh {ssid} {psk}") + os.system("sudo sh /home/pi/update.sh") + return "OK" , 200 + except Exception as err: + logger.error(f"Error: {err}") + return str(err) , 400 + +livestream_running = False +run_livestream = False + +@socketio.on("connect") +def on_connect(): + logger.debug("new device connected") + +@socketio.on("disconnect") +def on_disconnect(): + global run_livestream + logger.debug("device disconnected") + run_livestream = False + +@socketio.on("dropbox_auth_start") +def start_dropbox_auth(): + dbx = DropboxUploader() + url = dbx.start_auth_flow() + logger.info(url) + emit("dropbox_auth_url", url) + +@socketio.on("dropbox_auth_finish") +def finish_dropbox_auth(data): + dbx = DropboxUploader() + dbx.complete_auth_flow(data) + try: + user_details = dbx.get_user_details() + emit("dropbox_auth_complete", user_details.email) + except: + pass + +@socketio.on("dropbox_auth_get_user") +def get_auth_user(): + dbx = DropboxUploader() + try: + user_details = dbx.get_user_details() + if user_details: + emit("dropbox_auth_complete", user_details.email) + else: + emit("dropbox_auth_complete", None) + except: + emit("dropbox_auth_complete", None) + +@socketio.on("livestream") +def livestream(): + global livestream_running + global run_livestream + run_livestream = True + if livestream_running: + return + try: + with PiCamera(resolution=(640, 480)) as camera: + output = StreamingOutput() + logger.debug("Starting livestream") + camera.start_recording(output, format='mjpeg') + run_livestream = True + while run_livestream: + socketio.sleep(0) + if output.frame: + emit("livestream_data", output.frame) + except Exception as err: + emit("livestream_data", json.dumps({"error": err})) + logger.error(err) + livestream_running = False + logger.debug("Closed livestream") + +@socketio.on("close_livestream") +def close_livestream(): + global run_livestream + run_livestream = False + logger.debug("Closed livestream") + +def start_api_server(): + socketio.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/openoceancamera/base_camera.py b/openoceancamera/base_camera.py deleted file mode 100755 index 37fcffb..0000000 --- a/openoceancamera/base_camera.py +++ /dev/null @@ -1,111 +0,0 @@ -import time -import threading - -try: - from greenlet import getcurrent as get_ident - -except ImportError: - try: - from thread import get_ident - except ImportError: - from _thread import get_ident - -class CameraEvent(object): - """An Event-like class that signals all active clients when a new frame is - available. - """ - def __init__(self): - self.events = {} - - def wait(self): - """Invoked from each client's thread to wait for the next frame.""" - ident = get_ident() - if ident not in self.events: - # this is a new client - # add an entry for it in the self.events dict - # each entry has two elements, a threading.Event() and a timestamp - self.events[ident] = [threading.Event(), time.time()] - return self.events[ident][0].wait() - - def set(self): - """Invoked by the camera thread when a new frame is available.""" - now = time.time() - remove = None - for ident, event in self.events.items(): - if not event[0].isSet(): - # if this client's event is not set, then set it - # also update the last set timestamp to now - event[0].set() - event[1] = now - else: - # if the client's event is already set, it means the client - # did not process a previous frame - # if the event stays set for more than 5 seconds, then assume - # the client is gone and remove it - if now - event[1] > 5: - remove = ident - if remove: - del self.events[remove] - - def clear(self): - """Invoked from each client's thread after a frame was processed.""" - self.events[get_ident()][0].clear() - - -class BaseCamera(object): - thread = None # background thread that reads frames from camera - frame = None # current frame is stored here by background thread - last_access = 0 # time of last client access to the camera - event = CameraEvent() - - def __init__(self, time_duration=0): - """Start the background camera thread if it isn't running yet.""" - if BaseCamera.thread is None: - BaseCamera.last_access = time.time() - - # start background frame thread - BaseCamera.thread = threading.Thread(target=self._thread, args=(time_duration,)) - BaseCamera.thread.start() - - # wait until frames are available - while self.get_frame() is None: - time.sleep(0) - - def get_frame(self): - """Return the current camera frame.""" - BaseCamera.last_access = time.time() - - # wait for a signal from the camera thread - BaseCamera.event.wait() - BaseCamera.event.clear() - - return BaseCamera.frame - - @staticmethod - def frames(): - """"Generator that returns frames from the camera.""" - raise RuntimeError('Must be implemented by subclasses.') - - @classmethod - def _thread(cls, time_duration): - """Camera background thread.""" - print('Starting camera thread.') - frames_iterator = cls.frames() - timeout_start = time.time() - for frame in frames_iterator: - BaseCamera.frame = frame - BaseCamera.event.set() # send signal to clients - time.sleep(0) - - # if there hasn't been any clients asking for frames in - # the last 10 seconds then stop the thread - if time_duration > 0: - if time.time() > timeout_start + time_duration: - frames_iterator.close() - print("Finished streaming for given time", time_duration) - break - elif time.time() - BaseCamera.last_access > 10: - frames_iterator.close() - print('Stopping camera thread due to inactivity.') - break - BaseCamera.thread = None diff --git a/openoceancamera/camera/__init__.py b/openoceancamera/camera/__init__.py new file mode 100644 index 0000000..ba065d8 --- /dev/null +++ b/openoceancamera/camera/__init__.py @@ -0,0 +1,2 @@ +# from .capture import start_capture +from .camera_thread import camera_thread diff --git a/openoceancamera/camera/camera_thread.py b/openoceancamera/camera/camera_thread.py new file mode 100644 index 0000000..c2f1595 --- /dev/null +++ b/openoceancamera/camera/camera_thread.py @@ -0,0 +1,56 @@ +from Scheduler import Scheduler +from subsealight import PWM +import json +from .capture import start_capture +from .upload import start_upload +from datetime import datetime, timedelta +import os +from logger import logger + +def camera_thread(): + # load the schedule from the schedule json + camera_schedule = Scheduler() + PWM.switch_off() + + try: + with open("/home/pi/openoceancamera/schedule.json") as f: + data = json.load(f) + camera_schedule.load_scheduler_data(data) + except: + return + + logger.debug("In Camera thread") + while True: + # check if a schedule slot needs to run + slot_index = camera_schedule.should_start() + # if it needs to run, call the correct function to start the slot (photo/video) + if (slot_index >= 0): + slot = camera_schedule.get_slot(slot_index) + if(slot["upload"]): + start_upload(slot) + else: + start_capture(slot) + # else check when the next schedule is + next_slot = camera_schedule.next_future_timeslot() + slot_index = camera_schedule.should_start() + if slot_index == -1 and next_slot is not None: + # if the camera needs to shutdown, do wittypi stuff to shutdown the camera and set restart time and stop this loop + mins_to_next_slot = int(camera_schedule.time_to_nearest_schedule() / 60) + if mins_to_next_slot > 10: + shutdown_time = datetime.now() + timedelta(minutes=2) + shutdown_time = shutdown_time.strftime("%d %H:%M") + reboot_time = next_slot["start"] - timedelta(minutes=2) + reboot_time = reboot_time.strftime("%d %H:%M:%S") + + reboot_cmd = ( + 'sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 5 "' + reboot_time + '"' + ) + + os.system(reboot_cmd) + logger.info(f"The reboot time has been set to {reboot_time}") + shutdown_cmd = ( + 'sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 4 "' + shutdown_time + '"' + ) + os.system(shutdown_cmd) + logger.info(f"The camera will shut down at {shutdown_time}") + break diff --git a/openoceancamera/camera/capture.py b/openoceancamera/camera/capture.py new file mode 100644 index 0000000..2610967 --- /dev/null +++ b/openoceancamera/camera/capture.py @@ -0,0 +1,112 @@ +from picamera import PiCamera +from datetime import datetime +from time import sleep +import json +from constants import EXTERNAL_DRIVE +# from .sensors import readSensorData, writeSensorData +from sensors import Sensor +from logger import logger +from subsealight import PWM +from restart import reboot_camera +from .utils import get_camera_name +from wiper import run_wiper + +def capture_video(slot): + resolution = slot["resolution"] + framerate = slot["framerate"] + iso = slot["iso"] + exposure_mode = slot["exposure_mode"] + exposure_compensation = slot["exposure_compensation"] + light = slot["light"] + shutter_speed = slot["shutter_speed"] + try: + sensor = Sensor() + except Exception as err: + logger.error(err) + camera_name = get_camera_name() + wiper_status = slot["wiper"] + if wiper_status: + run_wiper(3) + try: + with PiCamera(resolution=resolution, framerate=framerate) as camera: + camera.iso = iso + camera.exposure_mode = exposure_mode + camera.exposure_compensation = exposure_compensation + camera.shutter_speed = shutter_speed + slot_name = f"{slot['start'].strftime('%Y-%m-%d_%H-%M-%S')}_{slot['stop'].strftime('%Y-%m-%d_%H-%M-%S')}.h264" + filename = f"{EXTERNAL_DRIVE}/{camera_name}_{slot_name}" + PWM.switch_on(light) + camera.start_recording(filename, format="h264") + current_time = datetime.now() + sensor.write_sensor_data() + sensor_data = sensor.get_sensor_data() + while current_time < slot["stop"]: + camera.annotate_text = f"{current_time.strftime('%Y-%m-%d %H:%M:%S')} @ {slot['framerate']} fps" + sensor.write_sensor_data() + sensor_data = sensor.get_sensor_data() + sleep(1) + current_time = datetime.now() + camera.stop_recording() + PWM.switch_off() + except Exception as err: + PWM.switch_off() + logger.error(err) + reboot_camera() + +def capture_images(slot): + try: + logger.debug("Going to set camera config") + resolution = slot["resolution"] + iso = slot["iso"] + exposure_mode = slot["exposure_mode"] + exposure_compensation = slot["exposure_compensation"] + light = slot["light"] + frequency = slot["frequency"] + shutter_speed = slot["shutter_speed"] + try: + sensor = Sensor() + except Exception as err: + logger.error(err) + camera_name = get_camera_name() + wiper_status = slot.get("wiper", False) + if wiper_status: + run_wiper(3) + logger.debug(f"Assigning camera config to {camera_name}") + try: + with PiCamera(resolution=resolution) as camera: + camera.iso = iso + camera.exposure_mode = exposure_mode + camera.exposure_compensation = exposure_compensation + camera.shutter_speed = shutter_speed + PWM.switch_on(light) + sensor.write_sensor_data() + sensor_data = sensor.get_sensor_data() + sensor_data["camera_name"] = camera_name + camera.exif_tags["IFDO.ImageDescription"] = json.dumps(sensor_data) + logger.debug("Entering continuous capture") + for f in camera.capture_continuous(f'{EXTERNAL_DRIVE}/{camera_name}_'+'img{timestamp:%Y-%m-%d-%H-%M-%S}.jpg', use_video_port=True): + PWM.switch_off() + currenttime = datetime.now() + if currenttime < slot["stop"]: + sleep(frequency-1) + sensor.write_sensor_data() + sensor_data = sensor.get_sensor_data() + sensor_data["camera_name"] = camera_name + camera.exif_tags["IFDO.ImageDescription"] = json.dumps(sensor_data) + else: + PWM.switch_off() + break + except Exception as err: + PWM.switch_off() + logger.error(err) + reboot_camera() + except Exception as err: + PWM.switch_off() + logger.error(err) + +def start_capture(slot): + logger.debug("Going to capture") + if slot["video"]: + capture_video(slot) + else: + capture_images(slot) diff --git a/openoceancamera/camera/upload.py b/openoceancamera/camera/upload.py new file mode 100644 index 0000000..2b4ff1b --- /dev/null +++ b/openoceancamera/camera/upload.py @@ -0,0 +1,31 @@ +import os +import json +from datetime import datetime, timedelta +from constants import EXTERNAL_DRIVE +from uploader import DropboxUploader +import logging + +logging.basicConfig(filename="system_logs.txt", format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO) +logger = logging.getLogger('main') +logger.setLevel(logging.DEBUG) + + +if not os.path.exists(os.path.join(EXTERNAL_DRIVE, "uploads.txt")): + with open(os.path.join(EXTERNAL_DRIVE, "uploads.txt"), 'w') as uploads: + pass + +def start_upload(slot): + logger.info("Starting upload slot") + upload_handler = DropboxUploader() + uploaded_files = [] + with open(os.path.join(EXTERNAL_DRIVE, "uploads.txt"), 'r') as uploads: + for filename in uploads: + uploaded_files.append(filename) + for root, dirs, files in os.walk(EXTERNAL_DRIVE): + for f in filter(lambda x: uploaded_files.index(x), sorted(files, reverse=True)): + if slot["stop"] > datetime.now(): + logger.info(f"Uploading {f}") + upload_handler.upload_file(os.path.join(root, f)) + logger.info(f"Uploaded {f}") + else: + break diff --git a/openoceancamera/camera/utils.py b/openoceancamera/camera/utils.py new file mode 100644 index 0000000..13a7757 --- /dev/null +++ b/openoceancamera/camera/utils.py @@ -0,0 +1,10 @@ +from logger import logger + +def get_camera_name(): + try: + with open("/home/pi/openoceancamera/camera_name.txt") as f: + name = f.read() + return name + except Exception as err: + logger.error(f"Error: {str(err)}") + return "OOCAM" \ No newline at end of file diff --git a/openoceancamera/camera_pi.py b/openoceancamera/camera_pi.py deleted file mode 100755 index 4cdcef8..0000000 --- a/openoceancamera/camera_pi.py +++ /dev/null @@ -1,23 +0,0 @@ -import io -import time -import picamera -from base_camera import BaseCamera - - -class Camera_Pi(BaseCamera): - def __init__(self, time_duration=0): - super().__init__(time_duration) - @staticmethod - def frames(): - with picamera.PiCamera() as camera: - - time.sleep(2) - - stream = io.BytesIO() - for _ in camera.capture_continuous(stream, 'jpeg', use_video_port=True): - - stream.seek(0) - yield stream.read() - - stream.seek(0) - stream.truncate() diff --git a/openoceancamera/constants.py b/openoceancamera/constants.py new file mode 100644 index 0000000..fed3745 --- /dev/null +++ b/openoceancamera/constants.py @@ -0,0 +1,7 @@ +import os + +SCHEDULE_FILE_PATH = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "schedule.json" +) +EXTERNAL_DRIVE = "/media/pi/OPENOCEANCA" +LOG_FILE = f"{EXTERNAL_DRIVE}/log.txt" diff --git a/openoceancamera/logger.py b/openoceancamera/logger.py new file mode 100644 index 0000000..ab0b16b --- /dev/null +++ b/openoceancamera/logger.py @@ -0,0 +1,5 @@ +import logging + +logging.basicConfig(filename="system_logs.txt", format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO) +logger = logging.getLogger('main') +logger.setLevel(logging.DEBUG) diff --git a/openoceancamera/main.py b/openoceancamera/main.py index b91d46e..77eaedb 100755 --- a/openoceancamera/main.py +++ b/openoceancamera/main.py @@ -1,628 +1,28 @@ import os import io import sys -import subprocess -import smbus import threading import base64 -import logging import json -from uuid import uuid1 from time import sleep, time, gmtime, strftime -from os import path from datetime import datetime, timedelta -from flask import Flask, request, send_file, jsonify, Response -from flask_cors import CORS -from flask_socketio import SocketIO, send, emit from picamera import PiCamera - -from StreamingOutput import StreamingOutput +from logger import logger +from camera import camera_thread +from sensors import Sensor from Scheduler import Scheduler -from camera_pi import Camera_Pi -from subsealight import PWM + +from appserver import start_api_server import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) -GPIO.setup(17, GPIO.OUT) - - -camera_name = "OpenOceanCamera" -camera = None -logging.basicConfig(filename="system_logs.txt", format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO) -logger = logging.getLogger('main') -logger.setLevel(logging.DEBUG) - -try: - with open("/home/pi/openoceancamera/camera_name.txt", "r") as camera_name_file: - camera_name = camera_name_file.read() -except Exception as err: - with open("/home/pi/openoceancamera/camera_name.txt", "w") as camera_name_file: - camera_name_file.write(camera_name) - -logger.info(f"Loaded camera name: {camera_name}") - -sys.path.append("/usr/lib/python3.5/") -sys.path.append("/usr/lib/python3/") -cmdoff = "sudo ifconfig wlan0 down" -cmdoff1 = "sudo service dnsmasq stop" - -try: - from Camera import Camera -except: - logger.error("Could not initialise camera") - -threads = [] - -app = Flask("OpenOceanCam") -app.config["CORS_HEADERS"] = "Content-Type" -CORS(app) -socketio = SocketIO(app, cors_allowed_origins="*", ping_timeout=5, ping_interval=5) - -external_drive = "/media/pi/OPENOCEANCA" -camera_config = [] -thread_active = False - -last_file_name = "" -stream_duration = 0 -lightSensor = -1.0 -temperatureSensor = -1.0 -pressureSensor = -1.0 -mstemperatureSensor = -1.0 -depthSensor = -1.0 - -def readSensorData(): - return { - "luminosity": lightSensor, - "temp": temperatureSensor, - "pressure": pressureSensor, - "mstemp": mstemperatureSensor, - "depth": depthSensor, - } - -def writeSensorData(sensor_data): - log_filename = f"{external_drive}/log.txt" - file_mode = None - if os.path.exists(log_filename): - file_mode = "a" - else: - file_mode = "w" - try: - with open(log_filename, file_mode) as f: - f.write( - json.dumps( - { - "timestamp": datetime.now().strftime( - "%m/%d/%Y, %H:%M:%S" - ), - "luminosity": sensor_data["luminosity"], - "temp": sensor_data["temp"], - "pressure": sensor_data["pressure"], - "mstemp": sensor_data["mstemp"], - "depth": sensor_data["depth"], - } - ) - ) - f.write("\n") - except: - logging.warn("Sensor data file did not exist. Making it now") - with open(log_filename, "w"): - pass - -def start_capture(video, slot): - global external_drive, last_file_name - global camera - filename = "" - print("start capture") - logger.info("Starting to capture") - try: - camera = Camera() - logger.info("Camera object initialised") - camera.set_iso(slot.get("iso", 0)) - camera.set_camera_resolution((int(slot["resolution"]["x"]), int(slot["resolution"]["y"]))) - camera.set_camera_exposure_mode(slot.get("exposure_mode", "auto")) - camera.set_camera_exposure_compensation(int(slot.get("exposure_compensation", 0))) - camera.set_shutter_speed(slot.get("shutter_speed", 0)) - logger.info(f"Finised setting up the camera for the slot {slot}") - try: - if not video: - camera.set_capture_frequency(slot["frequency"]) - filename = external_drive + "/" + datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + ".jpg" - last_file_name = filename - print("Capturing:") - #Don't do capture inside camera class - - PWM.switch_on(slot["light"]) - try: - logger.info("Going to capture continuous capture mode") - sensor_data = readSensorData() - writeSensorData(sensor_data) - sensor_data["camera_name"] = camera_name - camera.camera.exif_tags["IFD0.ImageDescription"] = json.dumps(sensor_data) - for f in camera.camera.capture_continuous(f"/media/pi/OPENOCEANCA/{camera_name}_" + 'img{timestamp:%Y-%m-%d-%H-%M-%S}.jpg', use_video_port=True): - if thread_active: - PWM.switch_off() - sleep(camera.frequency-1) - PWM.switch_on(slot["light"]) - currenttime=datetime.now() - if currenttime= 0: # if slot open - sensor_data = readSensorData() - writeSensorData(sensor_data) - light_mode = data[slot]["light"] - if not data[slot]["video"]: # slot for photo - start_capture(False, data[slot]) - else: - start_capture(True, data[slot]) - switch_flag = 0 - - else: - if switch_flag == 0: - logging.info("Stop: " + str(datetime.now())) - switch_flag = 1 - - next_slot = my_schedule.next_future_timeslot() - slot = my_schedule.should_start() - if next_slot is not None: - mins_to_next_slot = int(my_schedule.time_to_nearest_schedule() / 60) - print(f"We have {mins_to_next_slot} mins to next slot") - if (mins_to_next_slot > 10) and slot == -1: - logger.info("Camera is going to prepare to go to sleep") - sleeptime = datetime.now() + timedelta(minutes=5) - next_reboot = next_slot["start"] - timedelta(minutes=2) - sleeptime = sleeptime.strftime("%d %H:%M") - print(f"I will wake up at {next_reboot}") - logger.info(f"The reboot time has been set to {next_reboot}") - next_reboot = next_reboot.strftime("%d %H:%M:%S") - print(next_reboot) - startup_cmd = ( - 'sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 5 "' + next_reboot + '"' - ) - os.system(startup_cmd) - logger.info(startup_cmd) - print( - "raspberry pi is going to sleep now in 5 min, do not disturb" - ) - shutdown_cmd = ( - 'sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 4 "' + sleeptime + '"' - ) - os.system(shutdown_cmd) - logger.info(shutdown_cmd) - thread_active = False - break - except Exception as e: - if camera is not None: - camera.do_close() - print(e) - PWM.switch_off() - logger.error(e) - #threading.Thread(target=restart_code).start() - -def update_config(): - pass - -def restart_code(): - sleep(5) - os.system("sudo reboot") - -def reboot_camera(): - sleep(300) - os.system("sudo reboot") - -@app.route("/setCameraName", methods=["POST"]) -def set_camera_name(): - global camera_name - if request.method == 'POST': - camera_name = request.get_json()["name"] - with open("/home/pi/openoceancamera/camera_name.txt", "w") as camera_name_file: - camera_name_file.write(camera_name) - return camera_name - -@app.route("/syncTime", methods=["GET", "POST"]) -def sync_time(): - global thread_active - if request.method == "POST": - try: - data = request.get_json() - date_input = data["date"] - timezone = data["timezone"] - clear_cmd = ('sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 10 6') - os.system(clear_cmd) - os.system(f"sudo timedatectl set-timezone {timezone}") - os.system(f"sudo date -s '{date_input}'") - # Save the system time to RTC - - os.system("sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 1") - os.system("sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 2") - threading.Thread(target=restart_code).start() - return "OK", 200 - except Exception as err: - return str(err), 400 - -@app.route("/clearSchedule", methods=["GET"]) -def clearSchedule(): - try: - with open("/home/pi/openoceancamera/schedule.json", "w") as outfile: - json.dump(json.loads("[]"), outfile) - clear_cmd = ('sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 10 6') - os.system(clear_cmd) - threading.Thread(target=restart_code).start() - return "OK", 200 - except Exception as err: - return str(err), 400 - -@app.route("/setSchedule", methods=["POST", "GET"]) -def app_connect(): - global external_drive - global camera_config - global thread_active - if request.method == "POST": - thread_active = False - print(request.get_json()) - camera_config = request.get_json() - try: - with open("/home/pi/openoceancamera/schedule.json", "w") as outfile: - json.dump(camera_config, outfile) - date_input = camera_config[0]["date"] - timezone = camera_config[0]["timezone"] - try: - clear_cmd = ('sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 10 6') - os.system(clear_cmd) - os.system(f"sudo timedatectl set-timezone {timezone}") - print(timezone) - print(date_input) - # Sets the system time to the user's phone time - os.system(f"sudo date -s '{date_input}'") - # Save the system time to RTC - - os.system("sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 1") - os.system("sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 2") - pathv = path.exists(external_drive) - threading.Thread(target=restart_code).start() - if pathv: - thread_active = True - return "OK", 200 - else: - logger.error("Error: USB storage device not mounted") - return "Error: USB storage device not mounted", 400 - except Exception as err: - logger.error(err) - return str(err), 400 - except Exception as err: - logger.error(err) - return "Schedule could not be set", 400 - -@app.route("/viewConfig", methods=["GET"]) -def returnConfig(): - if request.method == "GET": - try: - response = { - "local_time": datetime.now().strftime("%d-%B-%Y %H:%M:%S"), - "local_timezone": str(datetime.utcnow().astimezone().tzinfo) , - "config": json.dumps(camera_config), - "camera_name": camera_name - } - return response, 200 - except Exception as err: - logger.error(err) - return str(err), 400 - -@app.route("/getLogs", methods=["GET"]) -def getLogs(): - if request.method == "GET": - try: - lines = int(request.args.get("lines")) + 1 - with open("/home/pi/system_logs.txt", 'r') as f: - data = f.read() - logs = data.split('\n') - result = "\n" - return result.join(logs[-lines:]) - except Exception as err: - logger.error(err) - return str(err), 400 - -@app.route("/clearLogs", methods=["GET"]) -def clearLogs(): - if request.method == "GET": - try: - open("/home/pi/system_logs.txt", 'w').close() - return "OK", 200 - except Exception as err: - return str(err), 400 - -@app.route("/testPhoto", methods=["POST", "GET"]) -def sendTestPic(): - camera = Camera() - if request.method == "POST": - try: - data = request.get_json(force=True) - print(data) - PWM.switch_on(data[0]["light"]) - camera.set_iso(data[0]["iso"]) - camera.set_shutter_speed(data[0]["shutter_speed"]) - camera.set_camera_exposure_mode(data[0].get("exposure_mode", 'auto')) - camera.set_camera_exposure_compensation(int(data[0].get("exposure_compensation", 0))) - if data[0].get("resolution", None): - camera.set_camera_resolution((int(data[0]["resolution"].get("x", 1920)), int(data[0]["resolution"].get("y", 1080)))) - - camera.do_capture("/home/pi/openoceancamera/test.jpg") - with open("/home/pi/openoceancamera/test.jpg", "rb") as image: - img_base64 = base64.b64encode(image.read()) - camera.do_close() - sensor_data = readSensorData() - response = { - "image": img_base64.decode("utf-8"), - "sensors": json.dumps(sensor_data), - } - PWM.switch_off() - return jsonify(response), 200 - except Exception as err: - logger.error(err) - camera.do_close() - PWM.switch_off() - return str(err), 400 - -@app.route("/testPhotoMem", methods=["POST", "GET"]) -def sendTestPicMem(): - if request.method == "POST": - data = request.get_json(force=True) - PWM.switch_on(data[0]["light"]) - flag = "SUCCESS" - pathv = path.exists(external_drive) - if pathv: - camera = None - try: - camera = Camera() - camera.set_iso(data[0]["iso"]) - camera.set_shutter_speed(data[0]["shutter_speed"]) - filename1 = external_drive + "/" + str(uuid1()) + ".jpg" - camera.do_capture(filename=filename1) - print("Written") - camera.do_close() - - camera = Camera() - camera.set_camera_resolution((1920, 1080)) - camera.set_camera_frame_rate(30) - filename2 = external_drive + "/" + str(uuid1()) + ".h264" - camera.do_record(filename=filename2) - print("Started recording") - sleep(3) - camera.do_close() - except Exception as err: - camera.do_close() - return "Please check if the USB storage device is connected properly", 400 - else: - return "USB storage device with name OOCAM required", 400 - PWM.switch_off() - sensor_data = readSensorData() - response = { - "sensors": json.dumps(sensor_data), - } - return jsonify(response), 200 - - -def gen(camera): - while True: - frame = camera.get_frame() - yield (b'--frame\r\n' - b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') - - -@app.route("/stream", methods=["GET", "POST"]) -def get_video(): - global stream_duration - if request.method == "GET": - return Response(gen(Camera_Pi(stream_duration)),mimetype='multipart/x-mixed-replace; boundary=frame' ) - if request.method == "POST": - time_duration = request.get_json()["time_duration"] - stream_duration = int(time_duration) - return "OK" - -def pull_updated_code(): - os.system("bash /home/pi/update.sh >> /home/pi/system_logs.txt 2>&1") - -@app.route("/update", methods=["GET","POST"]) -def update_code(): - if request.method == "POST": - try: - os.system("sudo sh /home/pi/openoceancamera/wittypi/wittycam.sh 10 6") - data = request.get_json() - ssid = data["ssid"] - psk = data["psk"] - os.system(f"sudo bash /home/pi/connect_to_wifi.sh {ssid} {psk}") - threading.Thread(target=pull_updated_code).start() - return "OK" , 200 - except Exception as err: - logger.error(f"Error: {err}") - return str(err) , 400 - -livestream_running = False -run_livestream = False - -@socketio.on("connect") -def on_connect(): - logger.debug("new device connected") - -@socketio.on("disconnect") -def on_disconnect(): - global run_livestream - logger.debug("device disconnected") - run_livestream = False - -@socketio.on("livestream") -def livestream(): - global livestream_running - global run_livestream - run_livestream = True - if livestream_running: - return - try: - with PiCamera(resolution=(640, 480)) as camera: - output = StreamingOutput() - logger.debug("Starting livestream") - camera.start_recording(output, format='mjpeg') - run_livestream = True - while run_livestream: - socketio.sleep(0) - if output.frame: - emit("livestream_data", output.frame) - except Exception as err: - emit("livestream_data", json.dumps({"error": err})) - logger.error(err) - livestream_running = False - logger.debug("Closed livestream") - -@socketio.on("close_livestream") -def close_livestream(): - global run_livestream - run_livestream = False - logger.debug("Closed livestream") - -def start_api_server(): - socketio.run(app, host="0.0.0.0", port=8000) - -def start_sensor_reading(): - global lightSensor, temperatureSensor, pressureSensor, mstemperatureSensor, depthSensor - while True: - try: - lightSensor = float( - str( - subprocess.check_output( - "python /home/pi/openoceancamera/TSL2561/Python/TSL2561.py", shell=True, text=True - ) - ) - ) - except Exception as e: - lightSensor = -1.0 - - try: - temperatureSensor = float( - str( - subprocess.check_output( - "python /home/pi/openoceancamera/tsys01-python/example.py", shell=True, text=True - ) - ) - ) - except Exception as e: - temperatureSensor = -1.0 - - try: - pressureSensorReadings = subprocess.check_output( - "python /home/pi/openoceancamera/ms5837-python/example.py", shell=True, text=True - ) - - pressureSensorReadings = pressureSensorReadings.split() - - pressureSensor, mstemperatureSensor, depthSensor = ( - float(pressureSensorReadings[0]), - float(pressureSensorReadings[1]), - float(pressureSensorReadings[2]), - ) - except Exception as e: - pressureSensor = -1.0 - mstemperatureSensor = -1.0 - depthSensor = -1.0 - if __name__ == "__main__": - # if len(sys.argv) < 2: - # print("Usage: python main.py ") - # exit(0) - try: - with open("/home/pi/openoceancamera/schedule.json") as f: - camera_config = json.load(f) - thread_active = True - logger.info("schedule opened, should start new thread") - except IOError as err: - logger.info(err) - finally: - api_thread = threading.Thread(target=start_api_server) - sensor_thread = threading.Thread(target=start_sensor_reading) - main_thread = threading.Thread(target=main) - api_thread.start() - main_thread.start() - sensor_thread.start() - logger.info("Started all threads") - sensor_thread.join() - main_thread.join() - api_thread.join() - logger.info("Program is shutting down") + api_thread = threading.Thread(target=start_api_server) + main_thread = threading.Thread(target=camera_thread) + api_thread.start() + main_thread.start() + logger.info("Started all threads") + main_thread.join() + api_thread.join() + logger.info("Program is shutting down") diff --git a/openoceancamera/ms5837-python/LICENSE b/openoceancamera/ms5837-python/LICENSE deleted file mode 100755 index ef810cd..0000000 --- a/openoceancamera/ms5837-python/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017 Blue Robotics - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/openoceancamera/ms5837-python/README.md b/openoceancamera/ms5837-python/README.md deleted file mode 100755 index 7b2ab4a..0000000 --- a/openoceancamera/ms5837-python/README.md +++ /dev/null @@ -1,134 +0,0 @@ -# ms5837-python - -A python module to interface with MS5837-30BA and MS5837-02BA waterproof pressure and temperature sensors. Tested on Raspberry Pi 3 with Raspbian. - -# Installation - -The python SMBus library must be installed. - - sudo apt-get install python-smbus - -Download this repository by clicking on the download button in this webpage, or using git: - -```sh -git clone https://github.com/bluerobotics/ms5837-python -``` - -If you would like to try the example, move to the directory where you downloaded the repository, and run `python example.py`. To use the library, copy the `ms5837.py` file to your project/program directory and use this import statement in your program: `import ms5837`. - -### Raspberry Pi - -If you are using a Raspberry Pi, the i2c interface must be enabled. Run `sudo raspi-config`, and choose to enable the i2c interface in the `interfacing options`. - -# Usage - - import ms5837 - -ms5837 provides a generic MS5837 class for use with different models - - MS5837(model=ms5837.MODEL_30BA, bus=1) - -These model-specific classes inherit from MS5837 and don't have any unique members - - MS5837_30BA(bus=1) - MS5837_02BA(bus=1) - -An MS5837 object can be constructed by specifiying the model and the bus - - sensor = ms5837.MS5837() # Use defaults (MS5837-30BA device on I2C bus 1) - sensor = ms5837.MS5837(ms5837.MODEL_02BA, 0) # Specify MS5837-02BA device on I2C bus 0 - -Or by creating a model-specific object - - sensor = ms5837.MS5837_30BA() # Use default I2C bus (1) - sensor = ms5837.MS5837_30BA(0) # Specify I2C bus 0 - -### init() - -Initialize the sensor. This needs to be called before using any other methods. - - sensor.init() - -Returns true if the sensor was successfully initialized, false otherwise. - -### read(oversampling=OSR_8192) - -Read the sensor and update the pressure and temperature. The sensor will be read with the supplied oversampling setting. Greater oversampling increases resolution, but takes longer and increases current consumption. - - sensor.read(ms5837.OSR_256) - -Valid arguments are: - - ms5837.OSR_256 - ms5837.OSR_512 - ms5837.OSR_1024 - ms5837.OSR_2048 - ms5837.OSR_4096 - ms5837.OSR_8192 - -Returns True if read was successful, False otherwise. - -### setFluidDensity(density) - -Sets the density in (kg/m^3) of the fluid for depth measurements. The default fluid density is ms5837.DENISTY_FRESHWATER. - - sensor.setFluidDensity(1000) # Set fluid density to 1000 kg/m^3 - sensor.setFluidDensity(ms5837.DENSITY_SALTWATER) # Use predefined saltwater density - -Some convenient constants are: - - ms5837.DENSITY_FRESHWATER = 997 - ms5837.DENSITY_SALTWATER = 1029 - -### pressure(conversion=UNITS_mbar) - -Get the most recent pressure measurement. - - sensor.pressure() # Get pressure in default units (millibar) - sensor.pressure(ms5837.UNITS_atm) # Get pressure in atmospheres - sensor.pressure(ms5837.UNITS_kPa) # Get pressure in kilopascal - -Some convenient constants are: - - ms5837.UNITS_Pa = 100.0 - ms5837.UNITS_hPa = 1.0 - ms5837.UNITS_kPa = 0.1 - ms5837.UNITS_mbar = 1.0 - ms5837.UNITS_bar = 0.001 - ms5837.UNITS_atm = 0.000986923 - ms5837.UNITS_Torr = 0.750062 - ms5837.UNITS_psi = 0.014503773773022 - -Returns the most recent pressure in millibar * conversion. Call read() to update. - -### temperature(conversion=UNITS_Centigrade) - -Get the most recent temperature measurement. - - sensor.temperature() # Get temperature in default units (Centigrade) - sensor.temperature(ms5837.UNITS_Farenheit) # Get temperature in Farenheit - -Valid arguments are: - - ms5837.UNITS_Centigrade - ms5837.UNITS_Farenheit - ms5837.UNITS_Kelvin - -Returns the most recent temperature in the requested units, or temperature in degrees Centigrade if invalid units specified. Call read() to update. - -### depth() - -Get the most recent depth measurement in meters. - - sensor.depth() - -Returns the most recent depth in meters using the fluid density (kg/m^3) configured by setFluidDensity(). Call read() to update. - -### altitude() - -Get the most recent altitude measurement relative to Mean Sea Level pressure in meters. - - sensor.altitude() - -Returns the most recent altitude in meters relative to MSL pressure using the density of air at MSL. Call read() to update. - diff --git a/openoceancamera/ms5837-python/example.py b/openoceancamera/ms5837-python/example.py deleted file mode 100755 index 36b1960..0000000 --- a/openoceancamera/ms5837-python/example.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/python -import ms5837 -import time - -sensor = ms5837.MS5837_30BA() # Default I2C bus is 1 (Raspberry Pi 3) -# sensor = ms5837.MS5837_30BA(0) # Specify I2C bus -# sensor = ms5837.MS5837_02BA() -# sensor = ms5837.MS5837_02BA(0) -# sensor = ms5837.MS5837(model=ms5837.MS5837_MODEL_30BA, bus=0) # Specify model and bus -try: - # We must initialize the sensor before reading it - if not sensor.init(): - print "Sensor could not be initialized" - exit(1) -except: - exit(1) - -# We have to read values from sensor to update pressure and temperature -if not sensor.read(): - print "Sensor read failed!" - exit(1) - -# print("Pressure: %.2f atm %.2f Torr %.2f psi") % ( -# sensor.pressure(ms5837.UNITS_atm), -# sensor.pressure(ms5837.UNITS_Torr), -# sensor.pressure(ms5837.UNITS_psi)) - -# print("Temperature: %.2f C %.2f F %.2f K") % ( -# sensor.temperature(ms5837.UNITS_Centigrade), -# sensor.temperature(ms5837.UNITS_Farenheit), -# sensor.temperature(ms5837.UNITS_Kelvin)) - -# freshwaterDepth = sensor.depth() # default is freshwater -sensor.setFluidDensity(ms5837.DENSITY_SALTWATER) -# saltwaterDepth = sensor.depth() # No nead to read() again -# sensor.setFluidDensity(1000) # kg/m^3 -# print("Depth: %.3f m (freshwater) %.3f m (saltwater)") % (freshwaterDepth, saltwaterDepth) - -# fluidDensity doesn't matter for altitude() (always MSL air density) -# print("MSL Relative Altitude: %.2f m") % sensor.altitude() # relative to Mean Sea Level pressure in air - -# time.sleep(5) - -# Spew readings -# while True: -if sensor.read(): - # print("P: %0.1f mbar %0.3f psi") % ( - # print("P: %0.1f mbar %0.3f psi\tT: %0.2f C %0.2f F") % ( - print "{} {} {}".format( - sensor.pressure(), sensor.temperature(), sensor.depth() - ) # Default is mbar (no arguments) -# sensor.pressure(ms5837.UNITS_psi)) # Request psi -# sensor.temperature(), # Default is degrees C (no arguments) -# sensor.temperature(ms5837.UNITS_Farenheit)) # Request Farenheit -else: - print ("Sensor read failed!") - exit(1) diff --git a/openoceancamera/requirements.txt b/openoceancamera/requirements.txt index 9235b3b..c7bd942 100755 --- a/openoceancamera/requirements.txt +++ b/openoceancamera/requirements.txt @@ -1,6 +1,8 @@ asn1crypto==0.24.0 astroid==2.2.5 attrs==20.3.0 +Adafruit-GPIO==1.0.3 +Adafruit-PureIO==1.1.5 certifi==2018.8.24 chardet==3.0.4 Click==7.0 @@ -37,6 +39,7 @@ six==1.12.0 smbus==1.1.post2 ssh-import-id==5.7 toml==0.10.2 +tsl2561==3.4.0 typed-ast==1.4.0 typing-extensions==3.7.4.3 urllib3==1.24.1 diff --git a/openoceancamera/restart.py b/openoceancamera/restart.py new file mode 100644 index 0000000..f9baad4 --- /dev/null +++ b/openoceancamera/restart.py @@ -0,0 +1,10 @@ +import os +from time import sleep + +def restart_code(): + sleep(5) + os.system("sudo reboot") + +def reboot_camera(): + sleep(300) + os.system("sudo reboot") \ No newline at end of file diff --git a/openoceancamera/sensors/__init__.py b/openoceancamera/sensors/__init__.py new file mode 100644 index 0000000..9034b07 --- /dev/null +++ b/openoceancamera/sensors/__init__.py @@ -0,0 +1,4 @@ +from .sensors import PressureSensor +from .sensors import LuminositySensor +from .sensors import TemperatureSensor +from .sensors import Sensor diff --git a/openoceancamera/sensors/gps.py b/openoceancamera/sensors/gps.py new file mode 100644 index 0000000..e7ed404 --- /dev/null +++ b/openoceancamera/sensors/gps.py @@ -0,0 +1,31 @@ +import time +import threading +import board +import busio +import adafruit_gps +from adafruit_gps import GPS_GtopI2C + +class GPS(GPS_GtopI2C): + def __init__(self): + super().__init__(board.I2C()) + self.send_command(b"PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0") + self.send_command(b"PMTK220,1000") + threading.Timer(1, self.update).start() + threading.Timer(5, self.request_firmware).start() + + def request_firmware(self): + self.send_command(b"PMTK605") + threading.Timer(5, self.request_firmware).start() + + def update(self): + super().update() + threading.Timer(1, self.update).start() + + +if __name__ == "__main__": + gps = GPS() + while not gps.has_fix: + continue + print("GPS fixed") + while gps.has_fix: + print(f"{gps.latitude}, {gps.longitude}") \ No newline at end of file diff --git a/openoceancamera/ms5837-python/ms5837.py b/openoceancamera/sensors/ms5837.py old mode 100755 new mode 100644 similarity index 50% rename from openoceancamera/ms5837-python/ms5837.py rename to openoceancamera/sensors/ms5837.py index 7d63e7e..7263daa --- a/openoceancamera/ms5837-python/ms5837.py +++ b/openoceancamera/sensors/ms5837.py @@ -1,8 +1,4 @@ -try: - import smbus -except: - print('Try sudo apt-get install python-smbus') - +import smbus2 as smbus from time import sleep # Models @@ -10,8 +6,8 @@ MODEL_30BA = 1 # Oversampling options -OSR_256 = 0 -OSR_512 = 1 +OSR_256 = 0 +OSR_512 = 1 OSR_1024 = 2 OSR_2048 = 3 OSR_4096 = 4 @@ -22,212 +18,235 @@ DENSITY_SALTWATER = 1029 # Conversion factors (from native unit, mbar) -UNITS_Pa = 100.0 -UNITS_hPa = 1.0 -UNITS_kPa = 0.1 -UNITS_mbar = 1.0 -UNITS_bar = 0.001 -UNITS_atm = 0.000986923 -UNITS_Torr = 0.750062 -UNITS_psi = 0.014503773773022 +UNITS_Pa = 100.0 +UNITS_hPa = 1.0 +UNITS_kPa = 0.1 +UNITS_mbar = 1.0 +UNITS_bar = 0.001 +UNITS_atm = 0.000986923 +UNITS_Torr = 0.750062 +UNITS_psi = 0.014503773773022 # Valid units UNITS_Centigrade = 1 -UNITS_Farenheit = 2 -UNITS_Kelvin = 3 +UNITS_Farenheit = 2 +UNITS_Kelvin = 3 + - class MS5837(object): - + # Registers - _MS5837_ADDR = 0x76 - _MS5837_RESET = 0x1E - _MS5837_ADC_READ = 0x00 - _MS5837_PROM_READ = 0xA0 - _MS5837_CONVERT_D1_256 = 0x40 - _MS5837_CONVERT_D2_256 = 0x50 - + _MS5837_ADDR = 0x76 + _MS5837_RESET = 0x1E + _MS5837_ADC_READ = 0x00 + _MS5837_PROM_READ = 0xA0 + _MS5837_CONVERT_D1_256 = 0x40 + _MS5837_CONVERT_D2_256 = 0x50 + def __init__(self, model=MODEL_30BA, bus=1): self._model = model - + try: self._bus = smbus.SMBus(bus) except: - print("Bus %d is not available.") % bus + print(("Bus %d is not available.") % bus) print("Available busses are listed as /dev/i2c*") self._bus = None - + self._fluidDensity = DENSITY_FRESHWATER self._pressure = 0 self._temperature = 0 self._D1 = 0 self._D2 = 0 - + def init(self): if self._bus is None: "No bus!" return False - + self._bus.write_byte(self._MS5837_ADDR, self._MS5837_RESET) - + # Wait for reset to complete sleep(0.01) - + self._C = [] - + # Read calibration values and CRC for i in range(7): - c = self._bus.read_word_data(self._MS5837_ADDR, self._MS5837_PROM_READ + 2*i) - c = ((c & 0xFF) << 8) | (c >> 8) # SMBus is little-endian for word transfers, we need to swap MSB and LSB + c = self._bus.read_word_data( + self._MS5837_ADDR, self._MS5837_PROM_READ + 2 * i + ) + c = ((c & 0xFF) << 8) | ( + c >> 8 + ) # SMBus is little-endian for word transfers, we need to swap MSB and LSB self._C.append(c) - + crc = (self._C[0] & 0xF000) >> 12 if crc != self._crc4(self._C): print("PROM read error, CRC failed!") return False - + return True - + def read(self, oversampling=OSR_8192): if self._bus is None: print("No bus!") return False - + if oversampling < OSR_256 or oversampling > OSR_8192: print("Invalid oversampling option!") return False - + # Request D1 conversion (temperature) - self._bus.write_byte(self._MS5837_ADDR, self._MS5837_CONVERT_D1_256 + 2*oversampling) - + self._bus.write_byte( + self._MS5837_ADDR, self._MS5837_CONVERT_D1_256 + 2 * oversampling + ) + # Maximum conversion time increases linearly with oversampling # max time (seconds) ~= 2.2e-6(x) where x = OSR = (2^8, 2^9, ..., 2^13) # We use 2.5e-6 for some overhead - sleep(2.5e-6 * 2**(8+oversampling)) - + sleep(2.5e-6 * 2 ** (8 + oversampling)) + d = self._bus.read_i2c_block_data(self._MS5837_ADDR, self._MS5837_ADC_READ, 3) self._D1 = d[0] << 16 | d[1] << 8 | d[2] - + # Request D2 conversion (pressure) - self._bus.write_byte(self._MS5837_ADDR, self._MS5837_CONVERT_D2_256 + 2*oversampling) - + self._bus.write_byte( + self._MS5837_ADDR, self._MS5837_CONVERT_D2_256 + 2 * oversampling + ) + # As above - sleep(2.5e-6 * 2**(8+oversampling)) - + sleep(2.5e-6 * 2 ** (8 + oversampling)) + d = self._bus.read_i2c_block_data(self._MS5837_ADDR, self._MS5837_ADC_READ, 3) self._D2 = d[0] << 16 | d[1] << 8 | d[2] # Calculate compensated pressure and temperature # using raw ADC values and internal calibration self._calculate() - + return True - + def setFluidDensity(self, denisty): self._fluidDensity = denisty - + # Pressure in requested units # mbar * conversion def pressure(self, conversion=UNITS_mbar): return self._pressure * conversion - + # Temperature in requested units # default degrees C def temperature(self, conversion=UNITS_Centigrade): degC = self._temperature / 100.0 if conversion == UNITS_Farenheit: - return (9.0/5.0)*degC + 32 + return (9.0 / 5.0) * degC + 32 elif conversion == UNITS_Kelvin: return degC + 273 return degC - + # Depth relative to MSL pressure in given fluid density def depth(self): - return (self.pressure(UNITS_Pa)-101300)/(self._fluidDensity*9.80665) - + return (self.pressure(UNITS_Pa) - 101300) / (self._fluidDensity * 9.80665) + # Altitude relative to MSL pressure def altitude(self): - return (1-pow((self.pressure()/1013.25),.190284))*145366.45*.3048 - + return (1 - pow((self.pressure() / 1013.25), 0.190284)) * 145366.45 * 0.3048 + # Cribbed from datasheet def _calculate(self): OFFi = 0 SENSi = 0 Ti = 0 - dT = self._D2-self._C[5]*256 + dT = self._D2 - self._C[5] * 256 if self._model == MODEL_02BA: - SENS = self._C[1]*65536+(self._C[3]*dT)/128 - OFF = self._C[2]*131072+(self._C[4]*dT)/64 - self._pressure = (self._D1*SENS/(2097152)-OFF)/(32768) + SENS = self._C[1] * 65536 + (self._C[3] * dT) / 128 + OFF = self._C[2] * 131072 + (self._C[4] * dT) / 64 + self._pressure = (self._D1 * SENS / (2097152) - OFF) / (32768) else: - SENS = self._C[1]*32768+(self._C[3]*dT)/256 - OFF = self._C[2]*65536+(self._C[4]*dT)/128 - self._pressure = (self._D1*SENS/(2097152)-OFF)/(8192) - - self._temperature = 2000+dT*self._C[6]/8388608 + SENS = self._C[1] * 32768 + (self._C[3] * dT) / 256 + OFF = self._C[2] * 65536 + (self._C[4] * dT) / 128 + self._pressure = (self._D1 * SENS / (2097152) - OFF) / (8192) + + self._temperature = 2000 + dT * self._C[6] / 8388608 # Second order compensation if self._model == MODEL_02BA: - if (self._temperature/100) < 20: # Low temp - Ti = (11*dT*dT)/(34359738368) - OFFi = (31*(self._temperature-2000)*(self._temperature-2000))/8 - SENSi = (63*(self._temperature-2000)*(self._temperature-2000))/32 - + if (self._temperature / 100) < 20: # Low temp + Ti = (11 * dT * dT) / (34359738368) + OFFi = ( + 31 * (self._temperature - 2000) * (self._temperature - 2000) + ) / 8 + SENSi = ( + 63 * (self._temperature - 2000) * (self._temperature - 2000) + ) / 32 + else: - if (self._temperature/100) < 20: # Low temp - Ti = (3*dT*dT)/(8589934592) - OFFi = (3*(self._temperature-2000)*(self._temperature-2000))/2 - SENSi = (5*(self._temperature-2000)*(self._temperature-2000))/8 - if (self._temperature/100) < -15: # Very low temp - OFFi = OFFi+7*(self._temperature+1500l)*(self._temperature+1500) - SENSi = SENSi+4*(self._temperature+1500l)*(self._temperature+1500) - elif (self._temperature/100) >= 20: # High temp - Ti = 2*(dT*dT)/(137438953472) - OFFi = (1*(self._temperature-2000)*(self._temperature-2000))/16 + if (self._temperature / 100) < 20: # Low temp + Ti = (3 * dT * dT) / (8589934592) + OFFi = (3 * (self._temperature - 2000) * (self._temperature - 2000)) / 2 + SENSi = ( + 5 * (self._temperature - 2000) * (self._temperature - 2000) + ) / 8 + if (self._temperature / 100) < -15: # Very low temp + OFFi = OFFi + 7 * (self._temperature + 1500) * ( + self._temperature + 1500 + ) + SENSi = SENSi + 4 * (self._temperature + 1500) * ( + self._temperature + 1500 + ) + elif (self._temperature / 100) >= 20: # High temp + Ti = 2 * (dT * dT) / (137438953472) + OFFi = ( + 1 * (self._temperature - 2000) * (self._temperature - 2000) + ) / 16 SENSi = 0 - - OFF2 = OFF-OFFi - SENS2 = SENS-SENSi - + + OFF2 = OFF - OFFi + SENS2 = SENS - SENSi + if self._model == MODEL_02BA: - self._temperature = (self._temperature-Ti) - self._pressure = (((self._D1*SENS2)/2097152-OFF2)/32768)/100.0 + self._temperature = self._temperature - Ti + self._pressure = (((self._D1 * SENS2) / 2097152 - OFF2) / 32768) / 100.0 else: - self._temperature = (self._temperature-Ti) - self._pressure = (((self._D1*SENS2)/2097152-OFF2)/8192)/10.0 - + self._temperature = self._temperature - Ti + self._pressure = (((self._D1 * SENS2) / 2097152 - OFF2) / 8192) / 10.0 + # Cribbed from datasheet def _crc4(self, n_prom): n_rem = 0 - - n_prom[0] = ((n_prom[0]) & 0x0FFF) + + n_prom[0] = (n_prom[0]) & 0x0FFF n_prom.append(0) - + for i in range(16): - if i%2 == 1: - n_rem ^= ((n_prom[i>>1]) & 0x00FF) + if i % 2 == 1: + n_rem ^= (n_prom[i >> 1]) & 0x00FF else: - n_rem ^= (n_prom[i>>1] >> 8) - - for n_bit in range(8,0,-1): + n_rem ^= n_prom[i >> 1] >> 8 + + for n_bit in range(8, 0, -1): if n_rem & 0x8000: n_rem = (n_rem << 1) ^ 0x3000 else: - n_rem = (n_rem << 1) + n_rem = n_rem << 1 + + n_rem = (n_rem >> 12) & 0x000F - n_rem = ((n_rem >> 12) & 0x000F) - self.n_prom = n_prom self.n_rem = n_rem - + return n_rem ^ 0x00 - + + class MS5837_30BA(MS5837): def __init__(self, bus=1): MS5837.__init__(self, MODEL_30BA, bus) - + + class MS5837_02BA(MS5837): def __init__(self, bus=1): MS5837.__init__(self, MODEL_02BA, bus) - + + diff --git a/openoceancamera/sensors/sensors.py b/openoceancamera/sensors/sensors.py new file mode 100644 index 0000000..6eab175 --- /dev/null +++ b/openoceancamera/sensors/sensors.py @@ -0,0 +1,211 @@ +import os +import json +from logger import logger +from datetime import datetime +from constants import LOG_FILE + +# sensors_logger = logging.getLogger(__name__) + +from .ms5837 import MS5837_30BA, DENSITY_SALTWATER, UNITS_Centigrade, UNITS_mbar +from .tsys01 import TSYS01_30BA, UNITS_Centigrade +from .tsl2561 import TSL2561_30BA +from .gps import GPS + + +class Sensor: + def __init__(self): + super().__init__() + self.pressure_data = -1 + self.temperature_data = -1 + self.luminosity_data = -1 + self.log_filename = LOG_FILE + + try: + self.gps = GPS() + except Exception as err: + logger.error(f"Sensor Error: {err}") + try: + self.pressure_sensor = PressureSensor() + except Exception as err: + logger.error(f"Sensor Error: {err}") + try: + self.temperature_sensor = TemperatureSensor() + except Exception as err: + logger.error(f"Sensor Error: {err}") + try: + self.luminosity_sensor = LuminositySensor() + except Exception as err: + logger.error(f"Sensor Error: {err}") + + def write_sensor_data(self): + if os.path.exists(self.log_filename): + file_mode = "a" + else: + file_mode = "w" + try: + self.read_sensor_data() + with open(self.log_filename, file_mode) as f: + f.write( + json.dumps( + { + "timestamp": datetime.now().strftime("%m/%d/%Y, %H:%M:%S"), + "luminosity": self.luminosity_data, + "temp": self.temperature_data, + "pressure": self.pressure_data, + "gps": { + "lat": self.gps.latitude, + "long": self.gps.longitude + } + } + ) + ) + f.write("\n") + except: + with open(self.log_filename, "w"): + pass + + def read_sensor_data(self): + try: + self.pressure_data = self.pressure_sensor.pressure() + except PressureSensorCannotReadException as err: + self.pressure_data = -1 + logger.error(f"Error: {err}") + except Exception as err: + logger.error(f"Sensor error: {err}") + pass + + try: + self.temperature_data = self.temperature_sensor.temperature() + except TemperatureSensorCannotReadException as err: + self.temperature_data = -1 + logger.error(f"Error: {err}") + except Exception as err: + logger.error(f"Sensor error: {err}") + pass + + try: + self.luminosity_data = self.luminosity_sensor.luminosity() + except LuminositySensorCannotReadException as err: + self.luminosity_data = -1 + logger.error(f"Error: {err}") + except Exception as err: + logger.error(f"Sensor error: {err}") + pass + + def get_sensor_data(self): + return { + "pressure": self.pressure_data, + "temperature" : self.temperature_data, + "luminosity" : self.luminosity_data, + "gps": { + "lat": self.gps.latitude, + "long": self.gps.longitude + } if self.gps else None + } + +class PressureSensorNotConnectedException(Exception): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class PressureSensorCannotReadException(Exception): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class PressureSensor(MS5837_30BA): + def __init__(self, bus=1): + super().__init__(bus=bus) + if not super().init(): + logger.warning("MS5837_30BA may not be connected") + raise PressureSensorNotConnectedException( + "MS5837_30BA may not be connected" + ) + self.setFluidDensity(DENSITY_SALTWATER) + + def pressure(self, conversion=UNITS_mbar): + if self.read(): + data = super().pressure(conversion=conversion) + logger.info(f"Reading pressure data from the sensor: {data}") + return data + else: + raise PressureSensorCannotReadException("Could not read pressure values") + + def temperature(self, conversion=UNITS_Centigrade): + if self.read(): + data = super().temperature(conversion=conversion) + logger.info(f"Reading temperature data from the sensor: {data}") + return data + else: + raise PressureSensorCannotReadException("Could not read temperature values") + + def depth(self): + if self.read(): + data = super().depth() + logger.info(f"Reading depth data from the sensor: {data}") + return data + else: + raise PressureSensorCannotReadException("Could not read depth values") + + +class TemperatureSensorNotConnectedException(Exception): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class TemperatureSensorCannotReadException(Exception): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class TemperatureSensor(TSYS01_30BA): + def __init__(self, bus=1): + super().__init__(bus=bus) + if not super().init(): + logger.warning("TSYS01_30BA may not be connected") + raise TemperatureSensorNotConnectedException( + "TSYS01_30BA may not be connected" + ) + + def temperature(self, conversion=UNITS_Centigrade): + if self.read(): + data = super().temperature(conversion=conversion) + logger.info(f"Reading temperature data from the sensor: {data}") + return data + else: + raise TemperatureSensorCannotReadException( + "Could not read temperature values" + ) + +class LuminositySensorNotConnectedException(Exception): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class LuminositySensorCannotReadException(Exception): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class LuminositySensor(TSL2561_30BA): + def __init__(self, bus=1): + super().__init__(bus=bus) + if not super().init(): + logger.warning("TSL2561_30BA may not be connected") + raise LuminositySensorNotConnectedException( + "TSL2561_30BA may not be connected" + ) + + def luminosity(self): + if self.read(): + data = super().lux() + logger.info(f"Reading luminosity data from the sensor: {data}") + return data + else: + raise LuminositySensorCannotReadException( + "Could not read luminosity values" + ) + +if __name__ == "__main__": + pass + diff --git a/openoceancamera/sensors/subsealight.py b/openoceancamera/sensors/subsealight.py new file mode 100644 index 0000000..33a7fe2 --- /dev/null +++ b/openoceancamera/sensors/subsealight.py @@ -0,0 +1,27 @@ +import RPi.GPIO as GPIO +import datetime +import sys +from logger import logger + +class SubSeaLight: + + def __init__(self): + GPIO.setwarnings(False) + # originally 11 + GPIO.setmode(GPIO.BCM) + GPIO.setup(24, GPIO.OUT) + try: + self.pwm = GPIO.PWM(24,500) # PIN 12 = Board 32 + except Exception as err: + logger.error(f"PWM not found") + + def switch_off(self): + self.pwm.ChangeDutyCycle(0) + self.pwm.stop() + + + def switch_on(self,dc): + print(dc) + self.pwm.start(dc) + + diff --git a/openoceancamera/sensors/tsl2561.py b/openoceancamera/sensors/tsl2561.py new file mode 100644 index 0000000..debfcad --- /dev/null +++ b/openoceancamera/sensors/tsl2561.py @@ -0,0 +1,334 @@ +import time +import typing +import smbus2 as smbus +from Adafruit_GPIO import I2C +from tsl2561.constants import * # pylint: disable=unused-wildcard-import,wildcard-import + +# Models +MODEL_30BA = 1 + + +class TSL2561: + """Driver for the TSL2561 digital luminosity (light) sensors.""" + + def __init__( + self, + address: typing.Optional[int] = None, + reset=0x1E, + busnum: typing.Optional[int] = None, + integration_time: int = TSL2561_INTEGRATIONTIME_402MS, + gain: int = TSL2561_GAIN_1X, + autogain: bool = False, + debug: bool = False, + bus=1, + model=MODEL_30BA, + ) -> None: + + # Set default address and bus number if not given + if address is not None: + self.address = address + else: + self.address = TSL2561_ADDR_FLOAT + + self.reset = reset + + if busnum is None: + self.busnum = 1 + + self._model = model + + try: + self._bus = smbus.SMBus(bus) + except: + print(("Bus %d is not available.") % bus) + print("Available busses are listed as /dev/i2c*") + self._bus = None + + self.i2c = I2C.get_i2c_device(self.address, busnum=busnum) + + self.debug = debug + self.integration_time = integration_time + self.gain = gain + self.autogain = autogain + + if self.integration_time == TSL2561_INTEGRATIONTIME_402MS: + self.delay_time = TSL2561_DELAY_INTTIME_402MS + + elif self.integration_time == TSL2561_INTEGRATIONTIME_101MS: + self.delay_time = TSL2561_DELAY_INTTIME_101MS + + elif self.integration_time == TSL2561_INTEGRATIONTIME_13MS: + self.delay_time = TSL2561_DELAY_INTTIME_13MS + + self._begin() + + def init(self): + if self._bus is None: + print("No bus!") + return False + + self._bus.write_byte(self.address, self.reset) + + # Wait for reset to complete + sleep(0.01) + + return True + + def _begin(self) -> None: + """Initializes I2C and configures the sensor (call this function before + doing anything else) + """ + # Make sure we're actually connected + x = self.i2c.readU8(TSL2561_REGISTER_ID) + + if not x & 0x0A: + raise Exception("TSL2561 not found!") + ########## + + # Set default integration time and gain + self.set_integration_time(self.integration_time) + self.set_gain(self.gain) + + # Note: by default, the device is in power down mode on bootup + self.disable() + + def enable(self) -> None: + """Enable the device by setting the control bit to 0x03""" + self.i2c.write8( + TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWERON + ) + + def disable(self) -> None: + """Disables the device (putting it in lower power sleep mode)""" + self.i2c.write8( + TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWEROFF + ) + + @staticmethod + def delay(value: int) -> None: + """Delay times must be specified in milliseconds but as the python + sleep function only takes (float) seconds we need to convert the sleep + time first + """ + time.sleep(value / 1000.0) + + def _get_data(self) -> typing.Tuple[int, int]: + """Private function to read luminosity on both channels""" + + # Enable the device by setting the control bit to 0x03 + self.enable() + + # Wait x ms for ADC to complete + TSL2561.delay(self.delay_time) + + # Reads a two byte value from channel 0 (visible + infrared) + broadband = self.i2c.readU16( + TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN0_LOW + ) + + # Reads a two byte value from channel 1 (infrared) + ir = self.i2c.readU16( + TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN1_LOW + ) + + # Turn the device off to save power + self.disable() + + return (broadband, ir) + + def set_integration_time(self, integration_time: int) -> None: + """Sets the integration time for the TSL2561""" + + # Enable the device by setting the control bit to 0x03 + self.enable() + + self.integration_time = integration_time + + # Update the timing register + self.i2c.write8( + TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, + self.integration_time | self.gain, + ) + + # Turn the device off to save power + self.disable() + + def set_gain(self, gain: int) -> None: + """Adjusts the gain on the TSL2561 (adjusts the sensitivity to light) + """ + + # Enable the device by setting the control bit to 0x03 + self.enable() + + self.gain = gain + + # Update the timing register + self.i2c.write8( + TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, + self.integration_time | self.gain, + ) + + # Turn the device off to save power + self.disable() + + def set_auto_range(self, value: bool) -> None: + """Enables or disables the auto-gain settings when reading + data from the sensor + """ + self.autogain = value + + def _get_luminosity(self) -> typing.Tuple[int, int]: + """Gets the broadband (mixed lighting) and IR only values from + the TSL2561, adjusting gain if auto-gain is enabled + """ + valid = False + + # If Auto gain disabled get a single reading and continue + if not self.autogain: + return self._get_data() + + # Read data until we find a valid range + _agcCheck = False + broadband = 0 + ir = 0 + + while not valid: + if self.integration_time == TSL2561_INTEGRATIONTIME_13MS: + _hi = TSL2561_AGC_THI_13MS + _lo = TSL2561_AGC_TLO_13MS + elif self.integration_time == TSL2561_INTEGRATIONTIME_101MS: + _hi = TSL2561_AGC_THI_101MS + _lo = TSL2561_AGC_TLO_101MS + else: + _hi = TSL2561_AGC_THI_402MS + _lo = TSL2561_AGC_TLO_402MS + + _b, _ir = self._get_data() + + # Run an auto-gain check if we haven't already done so ... + if not _agcCheck: + if _b < _lo and self.gain == TSL2561_GAIN_1X: + # Increase the gain and try again + self.set_gain(TSL2561_GAIN_16X) + # Drop the previous conversion results + _b, _ir = self._get_data() + # Set a flag to indicate we've adjusted the gain + _agcCheck = True + elif _b > _hi and self.gain == TSL2561_GAIN_16X: + # Drop gain to 1x and try again + self.set_gain(TSL2561_GAIN_1X) + # Drop the previous conversion results + _b, _ir = self._get_data() + # Set a flag to indicate we've adjusted the gain + _agcCheck = True + else: + # Nothing to look at here, keep moving .... + # Reading is either valid, or we're already at the chips + # limits + broadband = _b + ir = _ir + valid = True + else: + # If we've already adjusted the gain once, just return the new + # results. + # This avoids endless loops where a value is at one extreme + # pre-gain, and the the other extreme post-gain + broadband = _b + ir = _ir + valid = True + + return (broadband, ir) + + def _calculate_lux(self, broadband: int, ir: int) -> int: + """Converts the raw sensor values to the standard SI lux equivalent. + Returns 0 if the sensor is saturated and the values are unreliable. + """ + # Make sure the sensor isn't saturated! + if self.integration_time == TSL2561_INTEGRATIONTIME_13MS: + clipThreshold = TSL2561_CLIPPING_13MS + elif self.integration_time == TSL2561_INTEGRATIONTIME_101MS: + clipThreshold = TSL2561_CLIPPING_101MS + else: + clipThreshold = TSL2561_CLIPPING_402MS + + # Return 0 lux if the sensor is saturated + if broadband > clipThreshold or ir > clipThreshold: + raise Exception("Sensor is saturated") + + # Get the correct scale depending on the integration time + if self.integration_time == TSL2561_INTEGRATIONTIME_13MS: + chScale = TSL2561_LUX_CHSCALE_TINT0 + elif self.integration_time == TSL2561_INTEGRATIONTIME_101MS: + chScale = TSL2561_LUX_CHSCALE_TINT1 + else: + chScale = 1 << TSL2561_LUX_CHSCALE + + # Scale for gain (1x or 16x) + if not self.gain: + chScale = chScale << 4 + + # Scale the channel values + channel0 = (broadband * chScale) >> TSL2561_LUX_CHSCALE + channel1 = (ir * chScale) >> TSL2561_LUX_CHSCALE + + # Find the ratio of the channel values (Channel1/Channel0) + ratio1 = 0 + if channel0 != 0: + ratio1 = (channel1 << (TSL2561_LUX_RATIOSCALE + 1)) // channel0 + + # round the ratio value + ratio = (int(ratio1) + 1) >> 1 + + b = 0 + m = 0 + + if ratio >= 0 and ratio <= TSL2561_LUX_K1T: + b = TSL2561_LUX_B1T + m = TSL2561_LUX_M1T + elif ratio <= TSL2561_LUX_K2T: + b = TSL2561_LUX_B2T + m = TSL2561_LUX_M2T + elif ratio <= TSL2561_LUX_K3T: + b = TSL2561_LUX_B3T + m = TSL2561_LUX_M3T + elif ratio <= TSL2561_LUX_K4T: + b = TSL2561_LUX_B4T + m = TSL2561_LUX_M4T + elif ratio <= TSL2561_LUX_K5T: + b = TSL2561_LUX_B5T + m = TSL2561_LUX_M5T + elif ratio <= TSL2561_LUX_K6T: + b = TSL2561_LUX_B6T + m = TSL2561_LUX_M6T + elif ratio <= TSL2561_LUX_K7T: + b = TSL2561_LUX_B7T + m = TSL2561_LUX_M7T + elif ratio > TSL2561_LUX_K8T: + b = TSL2561_LUX_B8T + m = TSL2561_LUX_M8T + + temp = (channel0 * b) - (channel1 * m) + + # Do not allow negative lux value + if temp < 0: + temp = 0 + + # Round lsb (2^(LUX_SCALE-1)) + temp += 1 << (TSL2561_LUX_LUXSCALE - 1) + + # Strip off fractional portion + lux = temp >> TSL2561_LUX_LUXSCALE + + # Signal I2C had no errors + return lux + + def lux(self) -> int: + """Read sensor data, convert it to LUX and return it""" + broadband, ir = self._get_luminosity() + return self._calculate_lux(broadband, ir) + + +class TSL2561_30BA(TSL2561): + def __init__(self, bus=1): + TSL2561.__init__(self, MODEL_30BA, bus) + diff --git a/openoceancamera/tsys01-python/build/lib/tsys01/tsys01.py b/openoceancamera/sensors/tsys01.py old mode 100755 new mode 100644 similarity index 58% rename from openoceancamera/tsys01-python/build/lib/tsys01/tsys01.py rename to openoceancamera/sensors/tsys01.py index 1565d02..98495f3 --- a/openoceancamera/tsys01-python/build/lib/tsys01/tsys01.py +++ b/openoceancamera/sensors/tsys01.py @@ -1,70 +1,73 @@ -try: - import smbus -except: - print 'Try sudo apt-get install python-smbus' - +import smbus2 as smbus + from time import sleep +# Models +MODEL_30BA = 1 + # Valid units UNITS_Centigrade = 1 -UNITS_Farenheit = 2 -UNITS_Kelvin = 3 +UNITS_Farenheit = 2 +UNITS_Kelvin = 3 + - class TSYS01(object): - + # Registers - _TSYS01_ADDR = 0x77 - _TSYS01_PROM_READ = 0xA0 - _TSYS01_RESET = 0x1E - _TSYS01_CONVERT = 0x48 - _TSYS01_READ = 0x00 - - def __init__(self, bus=1): + _TSYS01_ADDR = 0x77 + _TSYS01_PROM_READ = 0xA0 + _TSYS01_RESET = 0x1E + _TSYS01_CONVERT = 0x48 + _TSYS01_READ = 0x00 + + def __init__(self, model=MODEL_30BA, bus=1): # Degrees C self._temperature = 0 self._k = [] - + + self._model = model + try: self._bus = smbus.SMBus(bus) except: - print("Bus %d is not available.") % bus + print(("Bus %d is not available.") % bus) print("Available busses are listed as /dev/i2c*") self._bus = None - - + def init(self): if self._bus is None: - "No bus!" + print("No bus!") return False - + self._bus.write_byte(self._TSYS01_ADDR, self._TSYS01_RESET) - + # Wait for reset to complete sleep(0.1) - + self._k = [] # Read calibration values # Read one 16 bit byte word at a time - for prom in range(0xAA, 0xA2-2, -2): + for prom in range(0xAA, 0xA2 - 2, -2): k = self._bus.read_word_data(self._TSYS01_ADDR, prom) - k = ((k & 0xFF) << 8) | (k >> 8) # SMBus is little-endian for word transfers, we need to swap MSB and LSB + k = ((k & 0xFF) << 8) | ( + k >> 8 + ) # SMBus is little-endian for word transfers, we need to swap MSB and LSB self._k.append(k) - + return True - + def read(self): if self._bus is None: - print "No bus!" + print("No bus!") return False - + # Request conversion self._bus.write_byte(self._TSYS01_ADDR, self._TSYS01_CONVERT) - + # Max conversion time = 9.04 ms sleep(0.01) - + adc = self._bus.read_i2c_block_data(self._TSYS01_ADDR, self._TSYS01_READ, 3) adc = adc[0] << 16 | adc[1] << 8 | adc[2] self._calculate(adc) @@ -74,18 +77,24 @@ def read(self): # default degrees C def temperature(self, conversion=UNITS_Centigrade): if conversion == UNITS_Farenheit: - return (9/5) * self._temperature + 32 + return (9 / 5) * self._temperature + 32 elif conversion == UNITS_Kelvin: return self._temperature - 273 return self._temperature # Cribbed from datasheet def _calculate(self, adc): - adc16 = adc/256 - self._temperature = -2 * self._k[4] * 10**-21 * adc16**4 + \ - 4 * self._k[3] * 10**-16 * adc16**3 + \ - -2 * self._k[2] * 10**-11 * adc16**2 + \ - 1 * self._k[1] * 10**-6 * adc16 + \ - -1.5 * self._k[0] * 10**-2 - - \ No newline at end of file + adc16 = adc / 256 + self._temperature = ( + -2 * self._k[4] * 10 ** -21 * adc16 ** 4 + + 4 * self._k[3] * 10 ** -16 * adc16 ** 3 + + -2 * self._k[2] * 10 ** -11 * adc16 ** 2 + + 1 * self._k[1] * 10 ** -6 * adc16 + + -1.5 * self._k[0] * 10 ** -2 + ) + + +class TSYS01_30BA(TSYS01): + def __init__(self, bus=1): + TSYS01.__init__(self, MODEL_30BA, bus) + diff --git a/openoceancamera/tsys01-python/LICENSE b/openoceancamera/tsys01-python/LICENSE deleted file mode 100755 index ef810cd..0000000 --- a/openoceancamera/tsys01-python/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017 Blue Robotics - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/openoceancamera/tsys01-python/README.md b/openoceancamera/tsys01-python/README.md deleted file mode 100755 index 5a8e615..0000000 --- a/openoceancamera/tsys01-python/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# tsys01-python - -A Python module to interface with the TSYS01 temperature sensor. Tested on Raspberry Pi 3 with Raspbian. - -The python SMBus library must be installed. - - sudo apt-get install python-smbus - -# Usage - - import tsys01 - -### TSYS01(bus=1) - - sensor = tsys01.TSYS01() # Use default I2C bus 1 - sensor = tsys01.TSYS01(0) # Specify I2C bus 0 - -### init() - -Initialize the sensor. This needs to be called before using any other methods. - - sensor.init() - -Returns true if the sensor was successfully initialized, false otherwise. - -### read() - -Read the sensor and update the temperature. - - sensor.read() - -Returns True if read was successful, False otherwise. - -### temperature(conversion=UNITS_Centigrade) - -Get the most recent temperature measurement. - - sensor.temperature() # Get temperature in default units (Centigrade) - sensor.temperature(ms5837.UNITS_Farenheit) # Get temperature in Farenheit - -Valid arguments are: - - tsys01.UNITS_Centigrade - tsys01.UNITS_Farenheit - tsys01.UNITS_Kelvin - -Returns the most recent temperature in the requested units, or temperature in degrees Centigrade if invalid units specified. Call read() to update. - - - diff --git a/openoceancamera/tsys01-python/bluerobotics_tsys01.egg-info/PKG-INFO b/openoceancamera/tsys01-python/bluerobotics_tsys01.egg-info/PKG-INFO deleted file mode 100755 index 98eb001..0000000 --- a/openoceancamera/tsys01-python/bluerobotics_tsys01.egg-info/PKG-INFO +++ /dev/null @@ -1,61 +0,0 @@ -Metadata-Version: 2.1 -Name: bluerobotics-tsys01 -Version: 0.0.1 -Summary: A python module for the TSYS01 digital temperature sensor -Home-page: https://www.bluerobotics.com -Author: Blue Robotics -Author-email: support@bluerobotics.com -License: UNKNOWN -Description: # tsys01-python - - A Python module to interface with the TSYS01 temperature sensor. Tested on Raspberry Pi 3 with Raspbian. - - The python SMBus library must be installed. - - sudo apt-get install python-smbus - - # Usage - - import tsys01 - - ### TSYS01(bus=1) - - sensor = tsys01.TSYS01() # Use default I2C bus 1 - sensor = tsys01.TSYS01(0) # Specify I2C bus 0 - - ### init() - - Initialize the sensor. This needs to be called before using any other methods. - - sensor.init() - - Returns true if the sensor was successfully initialized, false otherwise. - - ### read() - - Read the sensor and update the temperature. - - sensor.read() - - Returns True if read was successful, False otherwise. - - ### temperature(conversion=UNITS_Centigrade) - - Get the most recent temperature measurement. - - sensor.temperature() # Get temperature in default units (Centigrade) - sensor.temperature(ms5837.UNITS_Farenheit) # Get temperature in Farenheit - - Valid arguments are: - - tsys01.UNITS_Centigrade - tsys01.UNITS_Farenheit - tsys01.UNITS_Kelvin - - Returns the most recent temperature in the requested units, or temperature in degrees Centigrade if invalid units specified. Call read() to update. - -Platform: UNKNOWN -Classifier: Programming Language :: Python -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: OS Independent -Description-Content-Type: text/markdown diff --git a/openoceancamera/tsys01-python/bluerobotics_tsys01.egg-info/SOURCES.txt b/openoceancamera/tsys01-python/bluerobotics_tsys01.egg-info/SOURCES.txt deleted file mode 100755 index 738542e..0000000 --- a/openoceancamera/tsys01-python/bluerobotics_tsys01.egg-info/SOURCES.txt +++ /dev/null @@ -1,8 +0,0 @@ -README.md -setup.py -bluerobotics_tsys01.egg-info/PKG-INFO -bluerobotics_tsys01.egg-info/SOURCES.txt -bluerobotics_tsys01.egg-info/dependency_links.txt -bluerobotics_tsys01.egg-info/top_level.txt -tsys01/__init__.py -tsys01/tsys01.py \ No newline at end of file diff --git a/openoceancamera/tsys01-python/bluerobotics_tsys01.egg-info/dependency_links.txt b/openoceancamera/tsys01-python/bluerobotics_tsys01.egg-info/dependency_links.txt deleted file mode 100755 index 8b13789..0000000 --- a/openoceancamera/tsys01-python/bluerobotics_tsys01.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/openoceancamera/tsys01-python/bluerobotics_tsys01.egg-info/top_level.txt b/openoceancamera/tsys01-python/bluerobotics_tsys01.egg-info/top_level.txt deleted file mode 100755 index 8c4e31c..0000000 --- a/openoceancamera/tsys01-python/bluerobotics_tsys01.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -tsys01 diff --git a/openoceancamera/tsys01-python/build/lib/tsys01/__init__.py b/openoceancamera/tsys01-python/build/lib/tsys01/__init__.py deleted file mode 100755 index 33bd81f..0000000 --- a/openoceancamera/tsys01-python/build/lib/tsys01/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from tsys01 import TSYS01 diff --git a/openoceancamera/tsys01-python/dist/bluerobotics_tsys01-0.0.1-py3.7.egg b/openoceancamera/tsys01-python/dist/bluerobotics_tsys01-0.0.1-py3.7.egg deleted file mode 100755 index 2f420cc..0000000 Binary files a/openoceancamera/tsys01-python/dist/bluerobotics_tsys01-0.0.1-py3.7.egg and /dev/null differ diff --git a/openoceancamera/tsys01-python/example.py b/openoceancamera/tsys01-python/example.py deleted file mode 100755 index e037da0..0000000 --- a/openoceancamera/tsys01-python/example.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/python -import tsys01 -from time import sleep - -sensor = tsys01.TSYS01() -try: - if not sensor.init(): - print("Error initializing sensor") - exit(1) - - #while True: - if sensor.read(): - print("{:.2f}".format(sensor.temperature())) - else: - print("Error reading sensor") - exit(1) - # sleep(0.2) -except: - print("TSYS01 not connected") diff --git a/openoceancamera/tsys01-python/setup.py b/openoceancamera/tsys01-python/setup.py deleted file mode 100755 index f69348c..0000000 --- a/openoceancamera/tsys01-python/setup.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python - -from setuptools import setup, find_packages - -with open("README.md", "r") as f: - long_description = f.read() - -setup(name='bluerobotics-tsys01', - version='0.0.1', - description='A python module for the TSYS01 digital temperature sensor', - long_description=long_description, - long_description_content_type='text/markdown', - author='Blue Robotics', - author_email='support@bluerobotics.com', - url='https://www.bluerobotics.com', - packages=find_packages(), - classifiers=[ - "Programming Language :: Python", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ] - ) diff --git a/openoceancamera/tsys01-python/tsys01/__init__.py b/openoceancamera/tsys01-python/tsys01/__init__.py deleted file mode 100755 index 33bd81f..0000000 --- a/openoceancamera/tsys01-python/tsys01/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from tsys01 import TSYS01 diff --git a/openoceancamera/tsys01-python/tsys01/tsys01.py b/openoceancamera/tsys01-python/tsys01/tsys01.py deleted file mode 100755 index 1565d02..0000000 --- a/openoceancamera/tsys01-python/tsys01/tsys01.py +++ /dev/null @@ -1,91 +0,0 @@ -try: - import smbus -except: - print 'Try sudo apt-get install python-smbus' - -from time import sleep - -# Valid units -UNITS_Centigrade = 1 -UNITS_Farenheit = 2 -UNITS_Kelvin = 3 - - -class TSYS01(object): - - # Registers - _TSYS01_ADDR = 0x77 - _TSYS01_PROM_READ = 0xA0 - _TSYS01_RESET = 0x1E - _TSYS01_CONVERT = 0x48 - _TSYS01_READ = 0x00 - - def __init__(self, bus=1): - # Degrees C - self._temperature = 0 - self._k = [] - - try: - self._bus = smbus.SMBus(bus) - except: - print("Bus %d is not available.") % bus - print("Available busses are listed as /dev/i2c*") - self._bus = None - - - def init(self): - if self._bus is None: - "No bus!" - return False - - self._bus.write_byte(self._TSYS01_ADDR, self._TSYS01_RESET) - - # Wait for reset to complete - sleep(0.1) - - self._k = [] - - # Read calibration values - # Read one 16 bit byte word at a time - for prom in range(0xAA, 0xA2-2, -2): - k = self._bus.read_word_data(self._TSYS01_ADDR, prom) - k = ((k & 0xFF) << 8) | (k >> 8) # SMBus is little-endian for word transfers, we need to swap MSB and LSB - self._k.append(k) - - return True - - def read(self): - if self._bus is None: - print "No bus!" - return False - - # Request conversion - self._bus.write_byte(self._TSYS01_ADDR, self._TSYS01_CONVERT) - - # Max conversion time = 9.04 ms - sleep(0.01) - - adc = self._bus.read_i2c_block_data(self._TSYS01_ADDR, self._TSYS01_READ, 3) - adc = adc[0] << 16 | adc[1] << 8 | adc[2] - self._calculate(adc) - return True - - # Temperature in requested units - # default degrees C - def temperature(self, conversion=UNITS_Centigrade): - if conversion == UNITS_Farenheit: - return (9/5) * self._temperature + 32 - elif conversion == UNITS_Kelvin: - return self._temperature - 273 - return self._temperature - - # Cribbed from datasheet - def _calculate(self, adc): - adc16 = adc/256 - self._temperature = -2 * self._k[4] * 10**-21 * adc16**4 + \ - 4 * self._k[3] * 10**-16 * adc16**3 + \ - -2 * self._k[2] * 10**-11 * adc16**2 + \ - 1 * self._k[1] * 10**-6 * adc16 + \ - -1.5 * self._k[0] * 10**-2 - - \ No newline at end of file diff --git a/openoceancamera/uploader/__init__.py b/openoceancamera/uploader/__init__.py new file mode 100644 index 0000000..d669209 --- /dev/null +++ b/openoceancamera/uploader/__init__.py @@ -0,0 +1 @@ +from .dropbox_uploader import DropboxUploader \ No newline at end of file diff --git a/openoceancamera/uploader/dropbox_uploader.py b/openoceancamera/uploader/dropbox_uploader.py new file mode 100644 index 0000000..61c50e8 --- /dev/null +++ b/openoceancamera/uploader/dropbox_uploader.py @@ -0,0 +1,102 @@ +import os +import json +import pickle +import dropbox +from dropbox import DropboxOAuth2FlowNoRedirect + +import logging +logging.basicConfig(filename="system_logs.txt", format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO) +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +CREDENTIAL_STORE = os.path.join(os.environ["HOME"], ".oocam", "credentials") +APP_KEY=os.environ.get("DROPBOX_KEY", "") +APP_SECRET=os.environ.get("DROPBOX_SECRET", "") + +def load_credentials_file(): + if os.path.exists(os.path.dirname(CREDENTIAL_STORE)) and os.path.exists(CREDENTIAL_STORE): + with open(CREDENTIAL_STORE, 'rb') as f: + data = pickle.load(f) + return data + elif os.path.exists(os.path.dirname(CREDENTIAL_STORE)): + with open(CREDENTIAL_STORE, 'wb') as f: + pass + return None + else: + os.makedirs(os.path.dirname(CREDENTIAL_STORE)) + return None + +class DropboxUploader: + def __init__(self): + self.auth_flow = DropboxOAuth2FlowNoRedirect(APP_KEY, APP_SECRET, token_access_type="legacy") + self.oauth_result = load_credentials_file() + if self.oauth_result is not None: + self.isLoggedIn = True + else: + self.isLoggedIn = False + + def start_auth_flow(self): + """ + Returns: Authorisation URL + """ + url = self.auth_flow.start() + with open(CREDENTIAL_STORE, 'wb') as credential_store: + pickle.dump(self.auth_flow, credential_store) + return url + + def complete_auth_flow(self, auth_code): + try: + self.auth_flow = load_credentials_file() + self.oauth_result = self.auth_flow.finish(auth_code) + with open(CREDENTIAL_STORE, 'wb') as credential_store: + pickle.dump(self.oauth_result, credential_store) + self.isLoggedIn = True + except Exception as e: + logger.error(e) + + def get_user_details(self): + with dropbox.Dropbox(oauth2_access_token=self.oauth_result.access_token) as dbx: + return dbx.users_get_current_account() + + def upload_small_file(self, file_path): + with open(file_path, 'rb') as f: + filename = os.path.join("/", os.path.basename(f.name)) + data = f.read() + dbx = dropbox.Dropbox(oauth2_access_token=self.oauth_result.access_token) + upload_result = dbx.files_upload(data, filename, mode=dropbox.files.WriteMode.overwrite, mute=True) + return upload_result + + def upload_file(self, file_path): + with open(file_path, 'rb') as f: + filename = os.path.join("/", os.path.basename(f.name)) + dbx = dropbox.Dropbox(oauth2_access_token=self.oauth_result.access_token) + try: + metadata = dbx.files_get_metadata(filename) + if metadata: + logger.info(f"File alredy exists on server") + return metadata + except dropbox.exceptions.ApiError: + chunk_size = 4 * 1024 * 1024 + file_size = os.path.getsize(file_path) + if file_size < chunk_size: + return self.upload_small_file(file_path) + upload_session = dbx.files_upload_session_start(f.read(chunk_size)) + cursor = dropbox.files.UploadSessionCursor(session_id=upload_session.session_id, offset=f.tell()) + commit = dropbox.files.CommitInfo(path=filename) + while f.tell() < file_size: + if file_size - f.tell() <= chunk_size: + return dbx.files_upload_session_finish(f.read(chunk_size), cursor, commit) + else: + dbx.files_upload_session_append_v2(f.read(chunk_size), cursor) + cursor.offset = f.tell() + +if __name__ == "__main__": + dbx = DropboxUploader() + if not dbx.isLoggedIn: + print(DropboxUploader.start_auth_flow()) + code = input("Auth code: ") + DropboxUploader.complete_auth_flow(code) + logger.info("Logged in to Dropbox") + print("Starting upload") + dbx = DropboxUploader() + dbx.upload_file("/Users/utkarsh/Downloads/sample.pdf") \ No newline at end of file diff --git a/openoceancamera/wiper/__init__.py b/openoceancamera/wiper/__init__.py new file mode 100644 index 0000000..40c4fac --- /dev/null +++ b/openoceancamera/wiper/__init__.py @@ -0,0 +1 @@ +from .wiper import run_wiper diff --git a/openoceancamera/wiper/servo.py b/openoceancamera/wiper/servo.py new file mode 100644 index 0000000..7f74c65 --- /dev/null +++ b/openoceancamera/wiper/servo.py @@ -0,0 +1,25 @@ +import time +import wiringpi + +# use 'GPIO naming' +wiringpi.wiringPiSetupGpio() + +# set #18 to be a PWM output +wiringpi.pinMode(18, wiringpi.GPIO.PWM_OUTPUT) + +# set the PWM mode to milliseconds stype +wiringpi.pwmSetMode(wiringpi.GPIO.PWM_MODE_MS) + +# divide down clock +wiringpi.pwmSetClock(192) +wiringpi.pwmSetRange(2000) + +delay_period = 0.01 + +while True: + for pulse in range(50, 250, 1): + wiringpi.pwmWrite(18, pulse) + time.sleep(delay_period) + for pulse in range(250, 50, -1): + wiringpi.pwmWrite(18, pulse) + time.sleep(delay_period) diff --git a/openoceancamera/wiper/wiper.py b/openoceancamera/wiper/wiper.py new file mode 100644 index 0000000..229829f --- /dev/null +++ b/openoceancamera/wiper/wiper.py @@ -0,0 +1,30 @@ +import subprocess +import time +import sys + +SERVO_LOWER_LIMIT=50 +SERVO_UPPER_LIMIT=250 + +class Wiper: + def __init__(self): + subprocess.run("gpio -g mode 18 pwm", shell=True) + subprocess.run("gpio pwm-ms",shell=True) + subprocess.run("gpio pwmc 192",shell=True) + subprocess.run("gpio pwmr 2000",shell=True) + + def set_angle(self, angle): + pwm_output = (SERVO_UPPER_LIMIT * angle + SERVO_LOWER_LIMIT * (270-angle))/270 + subprocess.run(f"gpio -g pwm 18 {pwm_output}", shell=True) + +def run_wiper(sweeps): + wiper_front = Wiper() + wiper_front.set_angle(26) + for i in range(sweeps): + wiper_front.set_angle(68) + time.sleep(1) + wiper_front.set_angle(26) + time.sleep(2) + +if __name__ == "__main__": + run_wiper(10) + diff --git a/scripts/update.sh b/scripts/update.sh index 02b3b94..b400afd 100755 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -1,4 +1,5 @@ #!/bin/bash git reset --hard git pull -sudo mv /etc/wpa_supplicant/wpa_supplicant.conf.backup /etc/wpa_supplicant/wpa_supplicant.conf \ No newline at end of file +cp $HOME/etc/dhcpcd.conf /etc/dhcpcd.conf.backup +sudo mv /etc/wpa_supplicant/wpa_supplicant.conf.backup /etc/wpa_supplicant/wpa_supplicant.conf