Add 'car/' from commit 'eee0e8dc445691e600680f4abc77f2814b20b054'

git-subtree-dir: car
git-subtree-mainline: 1d29a5526c
git-subtree-split: eee0e8dc44
This commit is contained in:
Piv
2020-04-19 11:07:44 +09:30
93 changed files with 8401 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
import json
from DecisionSystem.messages import ConnectSwarm, SubmitVote, Message, deserialise, RequestVote, ClientVoteRequest, VoteResult
from multiprocessing import Pool
from messenger import Messenger
class BallotVoter:
def __init__(self, on_vote, handle_agreement, messenger: Messenger):
self.messenger = messenger
self.messenger.add_message_callback(self.on_message)
self.messenger.add_connect(self.on_connect)
self.on_vote = on_vote
self.handle_agreement = handle_agreement
def on_connect(self, rc):
print("Connected with result code " + str(rc))
# Tell commander we are now connected.
self.send_connect()
def on_message(self, message):
print("Message Received!")
messageD = deserialise(message.payload)
print("Message Type: " + messageD.type)
# Ok message.
if messageD.type == RequestVote().type:
print('Received vote message')
self.submit_vote()
elif messageD.type == "listening":
self.send_connect()
elif messageD.type == VoteResult.type:
self.handle_agreement(messageD.data["vote"])
def submit_vote(self):
v = self.on_vote()
if v == None:
print('Could not get vote')
return
print("Got Vote")
vote = SubmitVote(v, self.messenger.id)
print('Created Vote Message')
self.messenger.broadcast_message(self.messenger.swarm, vote.serialise())
print('published vote')
def send_connect(self):
# Send a connected message to let any commanders know that
# it is available.
self.messenger.broadcast_message(self.messenger.swarm, ConnectSwarm(self.messenger.id).serialise())
def request_vote(self):
"""Sends a request to the leader to start collecting votes."""
self.messenger.broadcast_message(self.messenger.swarm, ClientVoteRequest(self.messenger.id).serialise())

View File

@@ -0,0 +1,95 @@
from DecisionSystem.CentralisedDecision.ballotvoter import BallotVoter
from DecisionSystem.CentralisedDecision.messenger import MqttMessenger
import numpy as np
import cv2
import time
import argparse
import os.path
import sys
from GestureRecognition.simplehandrecogniser import SimpleHandRecogniser
from threading import Thread
from queue import Queue
import MyRaft.node as raft
import MyRaft.leader as leader
import DecisionSystem.CentralisedDecision.commander as commander
import DecisionSystem.CentralisedDecision.messenger as messenger
import DecisionSystem.CentralisedDecision.ballotvoter as voter
print("Parsing args")
parser = argparse.ArgumentParser(description="Runs a file with OpenCV and gets consensus from the swarm.")
parser.add_argument('-V', '--video', help="Path to video file.")
args = parser.parse_args()
recogniser = SimpleHandRecogniser(None)
# Checks if video file is specified and if that file exists.
if(args.video):
print('finding video')
if not os.path.isfile(args.video):
print("Input video file ", args.video, " doesn't exist")
sys.exit(1)
else:
# Exit if no video file specified - we aren't using webcam here.
sys.exit(1)
def on_vote():
# Get the current frame of the camera and process what hand
# is currently being seen.
print('getting frame')
# Need to copy rather than just take a reference, as frame will
# constantly be changing.
global vd
recogniser.set_frame(np.copy(vd.frame))
print('Got frame, voting with recogniser')
return recogniser.get_gesture()
def connect_to_broker(mqtt):
print("Connecting to broker")
max_collisions = 100
collisions = 1
while not mqtt.connect() and collisions <= max_collisions:
time.sleep(2 ** collisions - 1)
print("Reconnecting in %s" %(2 ** collisions - 1))
collisions += 1
mqtt = MqttMessenger()
v = BallotVoter(on_vote, mqtt)
def on_disconnect(rc):
print("Client disconnected from broker")
i = input("Would you like to reconnnect? (y|n)")
if i == 'y':
global mqtt
connect_to_broker(mqtt)
mqtt.add_disconnect_callback(on_disconnect)
connect_to_broker(mqtt)
# Start the video capture at the next whole minute.
current_time_sec = time.gmtime(time.time()).tm_sec
if current_time_sec < 40:
time.sleep(60 - current_time_sec)
else:
time.sleep(60 - current_time_sec + 60)
print('loading video')
print('Press q to quit the server, g to get votes/consensus')
while True:
if vd.frame is None:
continue
frame = np.copy(vd.frame)
cv2.imshow('Frame', frame)
k = cv2.waitKey(33)
if k == ord('q'):
break
elif k == -1:
continue
elif k == ord('g'):
# Get votes
pass

View File

@@ -0,0 +1,15 @@
from DecisionSystem.CentralisedDecision import commander
from DecisionSystem.CentralisedDecision.messenger import MqttMessenger
mqtt = MqttMessenger()
c = commander.Commander(mqtt, 10)
mqtt.connect()
f = input("Press any key and enter other than q to get current observation of the swarm: ")
while f != "q":
print("Vote is: ")
print(c.get_votes())
f = input("Press any key and enter other than q to get current observation of the swarm: ")
print("Thanks for trying!")

View File

@@ -0,0 +1,106 @@
"""This module provides an instance of the centralised, distributed voter"""
from queue import Queue
import json
import argparse
import numpy as np
import cv2
import MyRaft.node as raft
import MyRaft.leader as leader
import DecisionSystem.CentralisedDecision.commander as commander
import DecisionSystem.CentralisedDecision.messenger as messenger
import DecisionSystem.CentralisedDecision.ballotvoter as voter
import DecisionSystem.CentralisedDecision.videoget as videoget
import GestureRecognition.simplehandrecogniser as shr
import GestureRecognition.starkaleid as sk
class Instance:
"""An instance of the centralised, distributed approach to voting.
"""
def __init__(self, node_config='config.json', video_file=0):
with open(node_config) as f:
self.cfg= json.load(f)
self.mqtt = messenger.MqttMessenger(self.cfg)
self.we_lead = False
self.node = raft.RaftGrpcNode(node_config)
print("Node initialised")
self.node.add_state_change(self.on_state_changed)
self.voter = voter.BallotVoter(self.on_vote, self.handle_agreement, self.mqtt)
self.commander = commander.Commander(self.mqtt)
self.recogniser = shr.SimpleHandRecogniser(None)
self.last_vote = -1
self.q = Queue(5)
self.frame = None
self.vd = videoget.VideoGet(self.q, video_file)
self.kaleid = False
print("Initialised the instance")
def on_state_changed(self):
"""Callback method for state of the raft node changing"""
if isinstance(self.node._current_state, leader.Leader):
# We are now the commander (or leader)
self.commander = commander.Commander(self.mqtt)
else:
# No longer or never were a leader.
try:
del(self.commander)
except SyntaxError:
pass
def start(self):
self.vd.start()
self.mqtt.connect()
go = True
while go:
if self.kaleid:
go = self.show_kaleidoscope
else:
go = self.show_normal
def show_normal(self):
self.frame = np.copy(self.q.get())
cv2.imshow('Frame', self.frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
return False
elif cv2.waitKey(1) & 0xFF == ord('g'):
self.voter.request_vote()
def show_kaleidoscope(self):
self.frame = sk.make_kaleidoscope(np.copy(self.q.get()), 12)
cv2.imshow('Frame', self.frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
return False
elif cv2.waitKey(1) & 0xFF == ord('g'):
self.voter.request_vote()
def on_vote(self):
# Get the current frame of the camera and process what hand
# is currently being seen.
print('getting frame')
# Need to copy rather than just take a reference, as frame will
# constantly be changing.
self.recogniser.set_frame(np.copy(self.frame))
print('Got frame, voting with recogniser')
gesture = self.recogniser.get_gesture()
self.last_vote = gesture
return gesture
def handle_agreement(self, vote):
if vote == 5:
self.kaleid = True
else:
self.kaleid = False
parser = argparse.ArgumentParser(description="An instance of CAIDE")
if __name__ == "__main__":
instance = Instance(video_file="/Users/piv/Documents/Projects/Experiments/Camera1/video.mp4")
instance.start()

View File

@@ -0,0 +1,119 @@
import time
from DecisionSystem.messages import Message, CommanderWill, RequestVote, GetSwarmParticipants, deserialise, ClientVoteRequest, VoteResult
import json
import numpy as np
class Commander:
currentVote = None
# Stores voters that connect to maintain a majority.
# Voters who do not vote in latest round are removed.
_connectedVoters = []
# Dict has format: {clientId: vote}
_votes = {}
_taking_votes = False
def __init__(self, messenger, timeout = 60):
'''
Initial/default waiting time is 1 minute for votes to come in.
'''
self.timeout = timeout
self._messenger = messenger
self._messenger.add_connect(self.on_connect)
self._messenger.add_message_callback(self.on_message)
self._messenger.add_disconnect_callback(self.on_disconnect)
print('Connecting')
def make_decision(self):
# Should change this to follow strategy pattern, for different implementations of
# making a decision on the votes.
print("Making a decision")
votes = self._votes.values()
print(type(votes))
dif_votes = {}
for vote in votes:
# Get the count of different votes.
if vote in dif_votes:
dif_votes[vote] = dif_votes[vote] + 1
else:
dif_votes[vote] = 1
max_vote = ""
max_vote_num = 0
# Should try using a numpy array for this.
for vote in dif_votes.keys():
if dif_votes[vote] > max_vote_num:
max_vote = vote
max_vote_num = dif_votes[vote]
print("Made Decision!")
return max_vote
def get_votes(self):
# Should abstract messaging to another class.
print("Gathering Votes")
self._taking_votes = True
# Publish a message that votes are needed.
print("Sending request message")
self._messenger.broadcast_message(self._messenger.swarm, RequestVote(self._messenger.id).serialise())
print("published message")
time.sleep(self.timeout)
self._taking_votes = False
# TODO: Work out how to broadcast votes back to the swarm, maybe using raft?
return self.make_decision()
def on_message(self, message):
print("Message Received")
messageD = None
try:
messageD = deserialise(message.payload)
except:
print("Incorrect Message Has Been Sent")
return
# Need to consider that a malicious message may have a type with incorrect subtypes.
if messageD.type == "connect":
print("Voter connected!")
# Voter just connected/reconnnected.
if not messageD["client"] in self._connectedVoters:
self._connectedVoters.append(messageD["client"])
elif messageD.type == "vote":
print("Received a vote!")
# Voter is sending in their vote.
print(messageD.data["vote"])
print("From: ", messageD.sender)
if self._taking_votes:
# Commander must have requested their taking votes, and the timeout
# has not occurred.
# Only add vote to list if the client has not already voted.
if messageD.sender not in self._votes:
self._votes[messageD.sender] = int(messageD.data["vote"])
elif messageD.type == ClientVoteRequest().type:
# received a request to get votes/consensus.
self.get_votes()
elif messageD.type == "disconnected":
print("Voter disconnected :(")
self._connectedVoters.remove(messageD.sender)
def on_connect(self, rc):
# Subscribes now handled by the mqtt messenger, this is just here
# for convenience later.
pass
def get_participants(self):
self._messenger.broadcast_message(self._messenger.swarm, GetSwarmParticipants().serialise())
# Commander needs a will message too, for the decentralised version, so the
# voters know to pick a new commander.
# If using apache zookeeper this won't be needed.
# That's the wrong method for setting a will.
# self.client.publish("swarm1/voters", CommanderWill(self.client._client_id).serialise())
def on_disconnect(self, rc):
pass
def propogate_result(self, result):
self._messenger.broadcast_message(self._messenger.swarm, )

View File

@@ -0,0 +1,138 @@
import paho.mqtt.client as mqtt
import json
import random
class Messenger:
_connect_callbacks = []
_disconnect_callbacks = []
_message_callbacks = []
def broadcast_message(self, message):
"""
Broadcasts the specified message to the swarm based upon its topic(or group).
"""
raise NotImplementedError
def unicast_message(self, target, message):
"""
Broadcasts the specified message to the single target.
"""
raise NotImplementedError
def connect(self):
"""
Connect to the swarm.
"""
raise NotImplementedError
def disconnect(self):
"""
Disconnect from the swarm.
"""
raise NotImplementedError
def add_connect(self, connect):
"""
Adds a callback to do something else once we are connected.
"""
self._connect_callbacks.append(connect)
def on_connect(self, code = None):
"""
Called once the messenger connects to the swarm.
"""
for cb in self._connect_callbacks:
cb(code)
def on_disconnect(self, code = None):
"""
Called when the messenger is disconnected from the swarm.
"""
for cb in self._disconnect_callbacks:
cb(code)
def add_disconnect_callback(self, on_disconnect):
"""
Adds a callback for when the messenger is disconnected.
"""
self._disconnect_callbacks.append(on_disconnect)
def add_message_callback(self, on_message):
"""
Adds a callback
"""
self._message_callbacks.append(on_message)
def on_message(self, message):
"""
Called when the messenger receives a message.
"""
for cb in self._message_callbacks:
cb(message)
@property
def id(self):
"""
The id for this messenger that is being used in communication.
"""
raise NotImplementedError
@property
def swarm(self):
"""
Gets the name of the swarm this instance is a part of.
"""
raise NotImplementedError
class MqttMessenger(Messenger):
"""A messenger that uses MQTT."""
def __init__(self, configuration):
self._cfg = configuration
self._client = mqtt.Client(client_id=str(random.randint(0,500)))
self._client.on_connect = self.on_connect
self._client.on_message = self.on_message
self._client.on_disconnect = self.on_disconnect
def on_message(self, client, userdata, message):
Messenger.on_message(self, message)
def on_connect(self, client, userdata, flags, rc):
# Subscribe to the swarm specified in the config.
self._client.subscribe(self._cfg['mqtt']['swarm'])
# Also subscribe to our own topic for unicast messages.
self._client.subscribe(self._cfg['mqtt']['swarm'] + str(self._client._client_id))
Messenger.on_connect(self, rc)
def on_disconnect(self, client, userdata, rc):
Messenger.on_disconnect(self, rc)
def broadcast_message(self, message):
self._client.publish(self._cfg['mqtt']['swarm'], message, qos=1)
def unicast_message(self, target, message):
self._client.publish(target, message, qos=1)
def connect(self):
try:
self._client.connect(self._cfg['mqtt']['host'], \
int(self._cfg['mqtt']['port']), \
int(self._cfg['mqtt']['timeout']))
except:
print("Could not connect to broker")
return False
self._client.loop_start()
return True
def disconnect(self):
self._client.disconnect()
@property
def id(self):
return self._client._client_id
@property
def swarm(self):
return self._cfg['mqtt']['swarm']

View File

@@ -0,0 +1,45 @@
import numpy as np
import cv2
from threading import Thread
from queue import Queue
import time
class VideoGet:
'''
Code taken from Najam R Syed, available here:
https://github.com/nrsyed/computer-vision/tree/master/multithread
'''
def __init__(self, q, src):
'''
Must provide a source so we don't accidently start camera at work.
'''
self._stream = cv2.VideoCapture(src)
(self.grabbed, self.frame) = self._stream.read()
self.stopped = False
self.q = q
self.q.put(np.copy(self.frame))
self.src = src
def start(self):
Thread(target=self.get, args=()).start()
return self
def get(self):
while not self.stopped:
if not self.grabbed:
# self.stopped = True
print('frame not grabbed')
self._stream.release()
self._stream = cv2.VideoCapture(self.src)
# time.sleep(2)
self.grabbed, self.frame = self._stream.read()
else:
(self.grabbed, self.frame) = self._stream.read()
if self.q.full():
self.q.get()
self.q.put(np.copy(self.frame))
time.sleep(0.03) # Approximately 30fps
# Start a new feed.
def stop(self):
self.stopped = True

View File

@@ -0,0 +1,128 @@
import paho.mqtt.client as mqtt
import time
import json
import umsgpack
import numpy as np
class Voter:
'''
This class acts to replicate sensor information with the network to come to a consensus
of an activity occurrance. This is based upon research by Song et al. available at:
https://ieeexplore.ieee.org/document/5484586
The main advantage of this approach, as apposed to techniques such as by using zookeeper
or consul, is it can be completely decentralised and so works without a central server,
or needing to elect a central server. Additionally, it does not require all nodes
to run a Zookeeper/Consul server instance, which were not designed for these constrained
combat environments, which will fail if half the nodes fail, and also use a lot of resources
for handling services not required by this task.
The original approach in the paper requires some previous training before sensing, so
that there is a probability of a given action based upon the previous set of actions.
'''
_votes = {}
_connected_voters = []
_taking_votes = False
def __init__(self, on_vote, swarm_name):
'''
on_vote: Callback to get the required vote to broadcast.
'''
# Load config file
cfg = None
with open('config.json') as json_config:
cfg = json.load(json_config)
self._cfg = cfg
self.on_vote = on_vote
self._swarm = swarm_name
self._client = mqtt.Client()
self._client.on_message = self.on_message
self._client.on_connect = self.on_connect
self._client.connect(cfg["mqtt"]["host"], cfg["mqtt"]["port"], cfg["mqtt"]["timeout"])
self._client.loop_start()
def submit_vote(self):
# Publish to swarm where all other voters will receive a vote.
self._client.publish(self._swarm, self.collect_vote)
self._taking_votes = True
time.sleep(self._cfg["mqtt"]["timeout"])
self._taking_votes = False
# Wait a certain amount of time for responses, then fuse the information.
self.fuse_algorithm()
# Need the error and number of timestamps since voting started to finalise the consensus.
def fuse_algorithm(self):
# First calculate vi -> the actual vote that is taken
# (Or the probability that the observation is a label for each)
# We're just going to be doing 1 for the detected and 0 for all others.
# vi is for each hand (action in paper), but we're just going to do a single
# hand for our purposes. Will be able to use the CNN for all hands/gestures if we want to.
vi = np.zeros(6,1)
# Set correct vi.
vote = self.on_vote()
vi[vote] = 1
# Now send this off to the other nodes. Potentially using gossip...
# Set diagonal of ANDvi to elements of vi.
# This should actually be ANDvj, as it is for each observation received.
ANDvi = np.diag(vi.flatten())
# Nee
# M is the probability of going from one state to the next, which
# is assumed to be uniform for our situation - someone is just as likely
# to raise 5 fingers from two or any other.
# And so a 6x6 matrix is generated with all same probability to show this.
# Remember they could be holding up no fingers...
# m = np.full((6,6), 0.2)
# Y1T = np.full((6,1),1)
# Compute consensus state estimate by taking difference between our observations
# and all others individually.
# Moving to an approach that does not require the previous
# timestep (or so much math...)
# First take other information and fuse, using algorithm
# as appropriate.
pass
def custom_fuse(self):
vi = np.zeros(6,1)
# Set correct vi.
vote = self.on_vote()
vi[vote] = 1
def on_message(self, client, userdata, message):
try:
message_dict = umsgpack.unpackb(message.payload)
except:
print("Incorrect message received")
return
if message_dict["type"] == "vote":
# received a vote
if self._taking_votes:
self._votes[message_dict["client"]] = message_dict["vote"]
elif message_dict["type"] == "connect":
# voter connected to the swarm
self._connected_voters.append(message_dict["client"])
elif message_dict["type"] == "disconnect":
# Sent as the voter's will message
self._connected_voters.remove(message_dict["client"])
def on_connect(self, client, userdata, flags, rc):
print("Connected with result code " + str(rc))
self._client.subscribe(self._swarm)
def collect_vote(self):
vote_message = umsgpack.packb({"type": "vote",
"client":self._client._client_id, "vote": self.on_vote()})
return vote_message
def start_vote(self):
pass

View File

View File

@@ -0,0 +1,101 @@
import umsgpack
import uuid
class Message:
_type = None
def __init__(self, sender = "", data = {}):
self._sender = sender
self._data = data
@property
def sender(self):
return self._sender
@sender.setter
def sender(self, value):
self._sender = value
@property
def data(self):
return self._data
# I love using keywords...
@property
def type(self):
return self._type
@type.setter
def type(self, value):
self._type = value
def serialise(self):
return umsgpack.packb({"type":self.type, "sender": self.sender, "data": self.data})
# SHould make this static in Message class.
def deserialise(obj):
"""
Deserialises a given messagepack object into a Message.
"""
m = Message()
unpacked = umsgpack.unpackb(obj)
print('Unpacked Object')
print(unpacked)
m.type = unpacked["type"]
m._sender = unpacked["sender"]
m._data = unpacked["data"]
return m
class RequestLeader(Message):
_type = "RequestLeader"
class ProposeMessage(Message):
_type = "Propose"
class ElectionVote(Message):
_type = "Elect"
class Commit(Message):
_type = "Commit"
class ConnectSwarm(Message):
_type = "connect"
class RequestVote(Message):
_type = "reqvote"
class ConnectResponse(Message):
_type = "accepted"
class VoterWill(Message):
_type = "disconnectedvoter"
class CommanderWill(Message):
_type = "disconnectedcommander"
class SubmitVote(Message):
_type = "vote"
def __init__(self, vote = None, sender = "", data = {}):
Message.__init__(self, sender, data)
self._data["vote"] = vote
@property
def vote(self):
return self._data["vote"]
@vote.setter
def vote(self, value):
self._data["vote"] = value
class GetSwarmParticipants(Message):
_type = "listening"
class VoteResult(Message):
_type = "voteresult"
def __init__(self, vote, sender='', data={}):
super().__init__(sender=sender, data=data)
self._data["vote"] = vote
class ClientVoteRequest(Message):
_type = "clientvoterequest"