Compare commits
26 Commits
0d0729cb99
...
station2-c
| Author | SHA1 | Date | |
|---|---|---|---|
| b75e4b270b | |||
| a054e368cb | |||
| db1a261012 | |||
| 7b318831f2 | |||
| 48c532a81c | |||
| c302674958 | |||
| c2df56e15e | |||
| 93fd03e717 | |||
| 3682fc1448 | |||
| f67379f85d | |||
| ac4ff7a657 | |||
| e94f342739 | |||
| e421634432 | |||
| fba81ef345 | |||
| 888ed4db9d | |||
| e89b964d27 | |||
| d74b40b7e2 | |||
| 05e5785fc0 | |||
| 716f01a5be | |||
| 5fad6f294c | |||
| 9a771e8cd4 | |||
| 8a54cedb5b | |||
| 8e3b3abe8a | |||
| b50cfeaac8 | |||
| 8ac68b6ab0 | |||
| 502822a7a1 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,7 @@
|
||||
# Python
|
||||
__pycache__
|
||||
Pipfile
|
||||
Pipfile.lock
|
||||
|
||||
# CAD
|
||||
*.stl
|
||||
|
||||
BIN
CAD/StationA_CAD/StationA_Stack_Base.FCStd
Normal file
BIN
CAD/StationA_CAD/StationA_Stack_Base.FCStd
Normal file
Binary file not shown.
BIN
companion_software/FreeMono.ttf
Normal file
BIN
companion_software/FreeMono.ttf
Normal file
Binary file not shown.
22
companion_software/README.md
Normal file
22
companion_software/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
Setting up getty override
|
||||
=========================
|
||||
|
||||
```console
|
||||
systemctl edit getty@tty1.service
|
||||
```
|
||||
|
||||
add
|
||||
|
||||
```
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=-/opt/lewis-crawler/companion_software/dashboard/entrypoint.sh
|
||||
StandardInput=tty
|
||||
StandardOutput=tty
|
||||
```
|
||||
|
||||
then
|
||||
|
||||
```
|
||||
systemctl daemon-reload; systemctl restart getty@tty1.service
|
||||
```
|
||||
9
companion_software/entrypoint.sh
Executable file
9
companion_software/entrypoint.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd /opt/lewis-crawler/companion_software
|
||||
|
||||
git pull
|
||||
|
||||
pipenv install -r requirements.txt
|
||||
|
||||
pipenv run python -m houston -v
|
||||
43
companion_software/houston/__main__.py
Normal file
43
companion_software/houston/__main__.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# Lewis Crawler
|
||||
# 2021 - 2022
|
||||
# Kitsune Scientific
|
||||
|
||||
import logging
|
||||
import argparse
|
||||
import verboselogs
|
||||
import coloredlogs
|
||||
|
||||
from houston.houston import Houston
|
||||
|
||||
|
||||
# Get debug args
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'-d', '--debug',
|
||||
help="Print lots of debugging statements",
|
||||
action="store_const", dest="loglevel", const='DEBUG',
|
||||
default='WARNING',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
help="Be verbose",
|
||||
action="store_const", dest="loglevel", const='INFO',
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Install verbose logs
|
||||
verboselogs.install()
|
||||
|
||||
# Create a logger object.
|
||||
logger = logging.getLogger('Houston_Log')
|
||||
|
||||
# Install colored logs
|
||||
coloredlogs.install(level=args.loglevel,
|
||||
logger=logger,
|
||||
fmt='%(asctime)s,%(msecs)03d %(hostname)s %(levelname)s %(message)s') # noqa: E501
|
||||
|
||||
logger.info('Lewis Companion Software Started.')
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = Houston(logger)
|
||||
app.run()
|
||||
30
companion_software/houston/houston.py
Normal file
30
companion_software/houston/houston.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# Lewis Crawler
|
||||
# 2021 - 2022
|
||||
# Kitsune Scientific
|
||||
|
||||
import time
|
||||
|
||||
from houston.robotstreamer.streamer import RobotStreamer
|
||||
|
||||
|
||||
class Houston:
|
||||
def __init__(self, logger):
|
||||
# Setup logger
|
||||
self.log = logger
|
||||
|
||||
# Setup robotstreamer
|
||||
self.rs = RobotStreamer(self.log)
|
||||
|
||||
# We're ready to go!
|
||||
self.log.success('Ready to robot!')
|
||||
|
||||
def run(self):
|
||||
self.rs.run()
|
||||
|
||||
# Junk~
|
||||
while True:
|
||||
try:
|
||||
self.log.debug('Nothing to do.')
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
3
companion_software/houston/robotstreamer/__init__.py
Normal file
3
companion_software/houston/robotstreamer/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# Lewis Crawler
|
||||
# 2021 - 2022
|
||||
# Kitsune Scientific
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
@@ -0,0 +1 @@
|
||||
ffmpeg -f image2 -loop 1 -framerate 25 -video_size 1280x720 -r 25 -i Test-Pattern.jpg -vcodec libx264 -profile:v main -preset:v medium -r 30 -g 60 -keyint_min 60 -sc_threshold 0 -b:v 2500k -maxrate 2500k -bufsize 2500k -sws_flags lanczos+accurate_rnd -acodec aac -b:a 96k -ar 48000 -ac 2 -f flv -maxrate 55k "rtmp://rtmp.robotstreamer.com/live/4619?key=jEquBubjizYDMQNe8nKjLdu88iqFpNe3sVQmGRJ2tzbxd4QJrkSSEZBQhi9UTL3k"
|
||||
30
companion_software/houston/robotstreamer/streamer.py
Normal file
30
companion_software/houston/robotstreamer/streamer.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# Lewis Crawler
|
||||
# 2021 - 2022
|
||||
# Kitsune Scientific
|
||||
|
||||
import ffmpeg
|
||||
|
||||
|
||||
class RobotStreamer:
|
||||
def __init__(self, logger):
|
||||
self.log = logger
|
||||
|
||||
def run(self):
|
||||
self.log.debug('Running RobotStreamer')
|
||||
|
||||
stream = ffmpeg.input(
|
||||
'houston/robotstreamer/resources/Test-Pattern.jpg',
|
||||
f='image2',
|
||||
loop='1',
|
||||
framerate=25,
|
||||
video_size='1280x720')
|
||||
|
||||
stream = ffmpeg.output(
|
||||
stream,
|
||||
'http://robotstreamer.com/<secret>/1280/720',
|
||||
f='mpegts',
|
||||
bf=0,
|
||||
muxdelay=0.001,
|
||||
codec='mpeg1video')
|
||||
|
||||
ffmpeg.run(stream)
|
||||
BIN
companion_software/images/Test-Pattern.png
Normal file
BIN
companion_software/images/Test-Pattern.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -1,6 +0,0 @@
|
||||
import json
|
||||
import robot_util
|
||||
|
||||
url = '%s/v1/get_endpoint/jsmpeg_video_capture/%s' % ('https://api.robotstreamer.com', '4618')
|
||||
response = robot_util.getWithRetry(url)
|
||||
print(json.loads(response))
|
||||
79
companion_software/images/main.py
Normal file
79
companion_software/images/main.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import time
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
def testScreen(
|
||||
x=1280, y=720,
|
||||
image=None,
|
||||
stripeList=["lightgrey",
|
||||
"yellow",
|
||||
"cyan",
|
||||
"lightgreen",
|
||||
"magenta",
|
||||
"red",
|
||||
"blue"]):
|
||||
|
||||
if image is None:
|
||||
image = Image.new("RGB", (x, y))
|
||||
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
for i, stripe in enumerate(stripeList):
|
||||
draw.rectangle((
|
||||
(x/len(stripeList)) * i, 0,
|
||||
(x/len(stripeList)) * (i + 1), x),
|
||||
fill=stripe)
|
||||
|
||||
draw.rectangle((
|
||||
0, y - (y/3),
|
||||
x, y),
|
||||
fill="black")
|
||||
|
||||
for i, stripe in enumerate(stripeList):
|
||||
if i % 2 != 0:
|
||||
_fill = "black"
|
||||
else:
|
||||
_fill = stripeList[len(stripeList) - i - 1]
|
||||
|
||||
draw.rectangle((
|
||||
(x/len(stripeList)) * i, y - (y/3.1),
|
||||
(x/len(stripeList)) * (i + 1), y - (y/4.9)),
|
||||
fill=_fill)
|
||||
|
||||
draw.rectangle((
|
||||
(x/(len(stripeList)/4)), y - (y/5.3),
|
||||
(0), y),
|
||||
fill="darkblue")
|
||||
|
||||
draw.rectangle((
|
||||
(x/(len(stripeList)/2)), y - (y/5.3),
|
||||
(x/(len(stripeList)/1)), y),
|
||||
fill="white")
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def drawText(image):
|
||||
# get a font
|
||||
fnt = ImageFont.truetype("FreeMono.ttf", 50)
|
||||
# get a drawing context
|
||||
d = ImageDraw.Draw(image)
|
||||
|
||||
# draw multiline text
|
||||
d.multiline_text((10, 10), str(time.time()), font=fnt, fill=(0, 0, 0))
|
||||
|
||||
return image
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
blank_screen = testScreen()
|
||||
|
||||
while True:
|
||||
annotated_image = drawText(blank_screen.copy())
|
||||
|
||||
annotated_image.save('Test-Pattern.png')
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# testScreen().save('Test-Pattern.png')
|
||||
@@ -1,126 +0,0 @@
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
import ssl
|
||||
import urllib.request
|
||||
import getpass
|
||||
import json
|
||||
import _thread
|
||||
|
||||
|
||||
ConfigFilename = "/home/pi/config_" + getpass.getuser() + ".json" #this is never used
|
||||
|
||||
KeepAlivePeriod = 6
|
||||
|
||||
|
||||
def times(lst, number):
|
||||
return [x*number for x in lst]
|
||||
|
||||
|
||||
def getWithRetry(url, secure=True):
|
||||
|
||||
for retryNumber in range(2000):
|
||||
try:
|
||||
print("GET", url)
|
||||
if secure:
|
||||
object = urllib.request.urlopen(url)
|
||||
|
||||
else:
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
object = urllib.request.urlopen(url, context=ctx)
|
||||
|
||||
|
||||
|
||||
break
|
||||
except:
|
||||
print("could not open url", url)
|
||||
traceback.print_exc()
|
||||
time.sleep(2)
|
||||
|
||||
data = object.read()
|
||||
encoding = object.info().get_content_charset('utf-8')
|
||||
return data.decode(encoding)
|
||||
|
||||
|
||||
def getNoRetry(url, secure=True):
|
||||
#socket test has retry
|
||||
try:
|
||||
print("GET", url)
|
||||
if secure:
|
||||
object = urllib.request.urlopen(url)
|
||||
|
||||
else:
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
object = urllib.request.urlopen(url, context=ctx)
|
||||
|
||||
except:
|
||||
print("could not open url", url)
|
||||
return
|
||||
|
||||
data = object.read()
|
||||
encoding = object.info().get_content_charset('utf-8')
|
||||
return data.decode(encoding)
|
||||
|
||||
|
||||
def sendSerialCommand(ser, command):
|
||||
|
||||
|
||||
print(ser.name) # check which port was really used
|
||||
ser.nonblocking()
|
||||
|
||||
# loop to collect input
|
||||
#s = "f"
|
||||
#print "string:", s
|
||||
print(str(command.lower()))
|
||||
ser.write(command.lower() + "\r\n") # write a string
|
||||
#ser.write(s)
|
||||
ser.flush()
|
||||
|
||||
#while ser.in_waiting > 0:
|
||||
# print "read:", ser.read()
|
||||
|
||||
#ser.close()
|
||||
|
||||
|
||||
|
||||
def makePOST(url, data):
|
||||
|
||||
params = json.dumps(data).encode('utf8')
|
||||
req = urllib.request.Request(url,
|
||||
data=params,
|
||||
headers={'content-type': 'application/json'})
|
||||
response = urllib.request.urlopen(req)
|
||||
return response
|
||||
|
||||
|
||||
def sendCameraAliveMessage(apiServer, cameraID, streamKey):
|
||||
|
||||
print("sending camera alive message")
|
||||
url = '%s/v1/set_camera_status' % (apiServer)
|
||||
print("url", url)
|
||||
try:
|
||||
response = makePOST(url, {'camera_id': cameraID,
|
||||
'camera_status': 'online',
|
||||
'stream_key': streamKey,
|
||||
'type': 'robot_git'})
|
||||
#todo:send unified stream key here
|
||||
except:
|
||||
print("could not make post to", url)
|
||||
|
||||
|
||||
|
||||
def aplayFile(filename):
|
||||
for hardwareNumber in (2, 0, 1):
|
||||
_thread.start_new_thread(os.system, ('aplay -D plughw:%d,0 %s' % (hardwareNumber, filename),))
|
||||
|
||||
|
||||
def handleSoundCommand(command, keyPosition):
|
||||
print("command:", command, "key position:", keyPosition)
|
||||
if len(command) >= 6:
|
||||
if command[0:5] == "SOUND" and keyPosition == "down":
|
||||
number = int(command[5:])
|
||||
aplayFile('/home/pi/sound/SOUND%d.WAV' % number)
|
||||
3
companion_software/requirements.txt
Normal file
3
companion_software/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
coloredlogs
|
||||
verboselogs
|
||||
ffmpeg-python
|
||||
Reference in New Issue
Block a user