Skip to content
This repository has been archived by the owner on Dec 15, 2020. It is now read-only.

Capability refactor #53

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1875706
some initial changes to Blender plugin example using signals instead …
sphaero Jan 4, 2015
2c76704
added a parameter class managing parameter data
sphaero Jan 4, 2015
ddaf4a4
added unittest for ZOCPParameter
sphaero Jan 5, 2015
4b846e8
added logger
sphaero Jan 7, 2015
8ceb0f8
Merge branch 'master' into capability_refactor
sphaero Jan 7, 2015
90ff6e1
Merge branch 'master' into capability_refactor
sphaero Jan 7, 2015
22f47c3
adding to params_list should be done last
sphaero Jan 7, 2015
9aa6678
subscribe lists to lists (was dict)
sphaero Jan 7, 2015
0f920af
added params list test
sphaero Jan 7, 2015
bbbaa32
added signature argument to register methods
sphaero Jan 7, 2015
a74d3c3
removed parameter tracking through static member (conflicts with mult…
sphaero Jan 8, 2015
96724cc
added a parameter_list containing all parameters by index
sphaero Jan 8, 2015
078f3b6
slight code cleanup no logic changes
sphaero Jan 8, 2015
1ede544
fix parameter unittest
sphaero Jan 8, 2015
0938b29
added signal monitor test
sphaero Jan 13, 2015
f9c818d
some small fixes for signals
sphaero Jan 13, 2015
fd2491a
enabled _on_modified method
sphaero Jan 13, 2015
6c8c449
added set_object method and initial integration
sphaero Jan 13, 2015
0f1522d
Merge branch 'capability_refactor' of https://github.com/sphaero/pyZO…
sphaero Jan 13, 2015
1492e09
moved make_dict method to parameter.py
sphaero Jan 13, 2015
6515b40
removed signal sending when a modification is done
sphaero Jan 13, 2015
e31639b
monitor_subscribers now a list since a set can't be serialized by JSON
sphaero Jan 14, 2015
ced5cbb
increased travis verbosity
sphaero Jan 14, 2015
2164dab
logging didn't output
sphaero Jan 26, 2015
6eec790
fixed wrong var name
sphaero Jan 26, 2015
358de37
added param tests
sphaero Jan 26, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ install:
branches:
only:
- master
script: python tests/test_zocp.py
script: python -m unittest -v tests/test_zocp.py
54 changes: 30 additions & 24 deletions examples/BpyZOCP.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,46 +149,50 @@ def refresh_objects(self):

def _register_lamp(self, obj):
self.set_object(obj.name, "BPY_Lamp")
self.register_vec3f("location", obj.location[:])
#self.register_mat3f("worldOrientation", obj.worldOrientation[:])
self.register_vec3f("orientation", obj.rotation_euler[:])
self.register_vec3f("scale", obj.scale[:])
self.register_vec3f("color", obj.data.color[:])
self.register_float("energy", obj.data.energy)
self.register_float("distance", obj.data.distance)
self.register_vec3f("location", obj.location[:], 're')
#self.register_mat3f("worldOrientation", obj.worldOrientation[:], 're')
self.register_vec3f("orientation", obj.rotation_euler[:], 're')
self.register_vec3f("scale", obj.scale[:], 're')
self.register_vec3f("color", obj.data.color[:], 're')
self.register_float("energy", obj.data.energy, 're')
self.register_float("distance", obj.data.distance, 're')
#self.register_int ("state", obj.state)
#self.register_float("mass", obj.mass)

def _register_camera(self, obj):
self.set_object(obj.name, "BPY_Camera")
self.register_vec3f("location", obj.location[:])
#self.register_mat3f("worldOrientation", obj.worldOrientation[:])
self.register_vec3f("orientation", obj.rotation_euler[:])
self.register_float("angle", obj.data.angle, 'r')
self.register_float("shift_x", obj.data.shift_x, 'r')
self.register_float("shift_y", obj.data.shift_y, 'r')
self.register_vec3f("location", obj.location[:], 're')
#self.register_mat3f("worldOrientation", obj.worldOrientation[:], 're')
self.register_vec3f("orientation", obj.rotation_euler[:], 're')
self.register_float("angle", obj.data.angle, 're')
self.register_float("shift_x", obj.data.shift_x, 're')
self.register_float("shift_y", obj.data.shift_y, 're')

def _register_mesh(self, obj):
self.set_object(obj.name, "BPY_Mesh")
self.register_vec3f("location", obj.location[:])
#self.register_mat3f("worldOrientation", obj.worldOrientation[:])
self.register_vec3f("orientation", obj.rotation_euler[:])
self.register_vec3f("scale", obj.scale[:])
self.register_vec4f("color", obj.color[:])
#self.register_int ("state", obj.state)
#self.register_float("mass", obj.mass)
self.register_vec3f("location", obj.location[:], 're')
#self.register_mat3f("worldOrientation", obj.worldOrientation[:], 're')
self.register_vec3f("orientation", obj.rotation_euler[:], 're')
self.register_vec3f("scale", obj.scale[:], 're')
self.register_vec4f("color", obj.color[:], 're')
#self.register_int ("state", obj.state, 're')
#self.register_float("mass", obj.mass, 're')

def send_object_changes(self, obj):
self.set_object(obj.name, "BPY_Mesh")
if self._cur_obj.get("location", {}).get("value") != obj.location[:]:
self.register_vec3f("location", obj.location[:])
#self.register_vec3f("location", obj.location[:])
self.emit_signal("location", obj.location[:])
if self._cur_obj.get("orientation", {}).get("value") != obj.rotation_euler[:]:
self.register_vec3f("orientation", obj.rotation_euler[:])
#self.register_vec3f("orientation", obj.rotation_euler[:])
self.emit_signal("orientation", obj.rotation_euler[:])
if self._cur_obj.get("scale", {}).get("value") != obj.scale[:]:
self.register_vec3f("scale", obj.scale[:])
#self.register_vec3f("scale", obj.scale[:])
self.emit_signal("scale", obj.scale[:])
if obj.type == "LAMP":
if self._cur_obj.get("color", {}).get("value") != obj.data.color[:]:
self.register_vec3f("color", obj.data.color[:])
#self.register_vec3f("color", obj.data.color[:])
self.emit_signal("color", obj.data.color[:])
if self._cur_obj.get("energy", {}).get("value") != obj.data.energy[:]:
self.register_float("energy", obj.data.energy[:])
if self._cur_obj.get("distance", {}).get("value") != obj.data.distance[:]:
Expand All @@ -199,6 +203,8 @@ def send_object_changes(self, obj):
elif obj.type == "CAMERA":
self._register_camera(obj)

def emit_signal(self, name, data):
super().emit_signal(".".join(self._cur_obj_keys + (name, )), data)

#########################################
# Event methods. These can be overwritten
Expand Down
2 changes: 1 addition & 1 deletion examples/signal_subscriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def on_peer_enter(self, peer, name, *args, **kwargs):
split_name = name.split("@",1)
if(split_name[0] == 'subscribee'):
#self.signal_subscribe(self.get_uuid(), 'My String', peer, 'My String')
self.signal_subscribe(self.get_uuid(), 'Linked counter', peer, 'Counter')
self.signal_subscribe(self.get_uuid(), 1, peer, 2)


if __name__ == '__main__':
Expand Down
4 changes: 3 additions & 1 deletion examples/simple_node.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#!/usr/bin/python

from zocp import ZOCP
import logging
from zocp import ZOCP

if __name__ == '__main__':
zl = logging.getLogger("zocp")
zl.setLevel(logging.DEBUG)
sh = logging.StreamHandler()
zl.addHandler(sh)

z = ZOCP()
z.set_name("ZOCP-Test")
Expand Down
2 changes: 1 addition & 1 deletion src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__all__ = ['zocp']
__all__ = ['zocp', 'parameter']

from .zocp import ZOCP
251 changes: 251 additions & 0 deletions src/parameter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import logging
import json
import uuid
from collections import MutableSequence

logger = logging.getLogger(__name__)

def make_dict(keys, value):
"""
returns a nested dict
keys argument must be a list
"""
a = {}
d = a
for key in keys[:-1]:
d = d[key]
d[keys[-1]] = value
return a

class ZOCPParameterList(MutableSequence):
"""
A container for manipulating lists of parameters

Perhaps we should use weakrefs:
https://docs.python.org/2/library/weakref.html#weak-reference-objects

It's also easier to just use a dict but we need this logic in C as
well
"""
def __init__(self):
"""Initialize the class"""
self._list = list()
self._free_idx = list() # list of free indexes

def __len__(self):
"""List length"""
return len(self._list)

def __getitem__(self, ii):
"""Get a list item"""
return self._list[ii]

def __delitem__(self, ii):
"""Delete an item by marking"""
if ii >= len(self._list):
raise IndexError("Index {0} to remove is beyond list boundary".format(ii))
# ii can be negative so convert to real index
idx = ii % len(self._list)
print("deleting idx {0}".format(idx))
if idx == len(self._list)-1:
self._list.pop()
return
self._list[idx] = None
self._free_idx.append(idx)

def __setitem__(self, ii, val):
self._list[ii] = val

def __str__(self):
return "ZOCPParameterList:"+ str(self._list)

def __repr__(self):
return self._list

def insert(self, param):
if param.sig_id == None:
# find a free spot in the list
try:
param.sig_id = self._free_idx.pop(0)
except IndexError:
param.sig_id = len(self._list)
self._list.append(param)
else:
print("reusing id", param.sig_id, )
self._list[param.sig_id] = param
elif param.sig_id == len(self._list):
self._list.append(param)
else:
self._list[param.sig_id] = param

def append(self, param):
raise NotImplemented("Append is not implemented, use insert")

def clear(self):
# http://bugs.python.org/issue11388
try:
while True:
param = self.pop()
param.sig_id = None
except IndexError:
pass


class ZOCPParameter(object):
"""
Wrapper class for parameters used through ZOCP
"""

def __init__(self, znode, value, name, access, type_hint, signature, min=None, max=None, step=None, sig_id=None, *args, **kwargs):
self._value = value
# init meta data
self._znode = znode # reference to the ZOCPNode instance
self.name = name # name of the parameter
self.min = min # minimum value of the parameter (optional)
self.max = max # maximum value of the parameter (optional)
self.step = step
self.access = access # description of access methods (Read,Write,signal Emitter,Signal receiver)
self.type_hint = type_hint # a hint of the type of data
self.signature = signature # signature describing the parameter in memory
self._subscribers = [] # list of peer receivers for emitted signals in case we're an emitter
self._sig_id = sig_id # the id of the parameter (needed for referencing to other nodes)

# get the params_list and monitor_list before we get extra meta data!
self._params_list = kwargs.pop('params_list', None)
self._monitor_subscribers = kwargs.pop("monitor_list", None)

self.extended_meta = kwargs # optional extra meta data

# in case we're an emitter overwrite the set method
if 'e' in self.access:
self.set = self._set_emit

if self._params_list == None:
self._params_list = self._znode._parameter_list
if self._monitor_subscribers == None:
self._monitor_subscribers = self._znode.monitor_subscribers
# get ourselves an sig_id by inserting in the params_list
self._params_list.insert(self)

def _set_emit(self, value):
"""
Set and emit value as a signal
"""
self._value = value
msg = json.dumps({'SIG': [self.sig_id, self._value]})
for peer, recv_id in self._subscribers:
self._znode.whisper(uuid.UUID(peer), msg.encode('utf-8'))
for peer in self._monitor_subscribers:
self._znode.whisper(peer, msg.encode('utf-8'))

def set_sig_id(self, sig_id):
if self._sig_id != None and sig_id != None:
logger.warning("ZOCPParameter signal id is overwritten from"\
+" {0} to {1}".format(self._sig_id, sig_id))
self._sig_id = sig_id

def get_sig_id(self):
return self._sig_id

def set_object(self, obj):
"""
Set object path

we need this in order to find ourselves in the capability tree
"""
self._object = obj

def get(self):
return self._value

def set(self, value):
self._value = value

def subscribe_receiver(self, recv_peer, receiver_id):
# update subscribers list
# TODO: I'm not sure we need to register the receiver_id???
subscriber = (recv_peer.hex, receiver_id)
if subscriber not in self._subscribers:
self._subscribers.append(subscriber)
if self._object:
data = make_dict(self._object, {"subscribers": self._subscribers})
else:
data = { self.name: {"subscribers": self._subscribers}}
self._znode._on_modified(data=data)

def unsubscribe_receiver(self, recv_peer, receiver_id):
# update subscribers list
# TODO: I'm not sure we need to register the receiver_id???
subscriber = (recv_peer.hex, receiver_id)
if subscriber in self._subscribers:
self._subscribers.remove(subscriber)
if self._object:
data = make_dict(self._object, {"subscribers": self._subscribers})
else:
data = { self.name: {"subscribers": self._subscribers}}
self._znode._on_modified(data=data)

def _to_bytes(self):
"""
converts value to an array of bytes

ref: https://docs.python.org/2/library/stdtypes.html#memoryview
"""
return struct.pack(self.signature, self.value)

def to_dict(self):
"""
Converts this parameter to a representing dictionary
"""
d = self.extended_meta
d['name'] = self.name
d['value'] = self._value
if self.min:
d['min'] = self.min
if self.max:
d['max'] = self.max
if self.step:
d['step'] = self.step
d['access'] = self.access
d['typeHint'] = self.type_hint
d['sig'] = self.signature
d['sig_id'] = self.sig_id
if 'e' in self.access:
d['subscribers'] = self._subscribers
return d

def __str__(self):
return str(self.to_dict())

def __repr__(self):
return "ZOCPParameter({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8})"\
.format("znode", self._value, self.name, self.access, self.type_hint, self.signature, self.min, self.max, self.step, self.sig_id)
#return self.to_dict().__repr__()

def __dict__(self):
return to_dict()

def remove(self):
# try to remove itself from the params_list
# could already be done by clear()
try:
self._params_list.remove(self)
except ValueError:
pass

value = property(get, set)
sig_id = property(get_sig_id, set_sig_id)


if __name__ == '__main__':
plist = ZOCPParameterList()
mlist = []
param1 = ZOCPParameter(None, 1, 'param1', 'rwes', None, 'i', params_list=plist, monitor_list=mlist)
param2 = ZOCPParameter(None, 0.1, 'param2', 'rw', None, 'f', params_list=plist, monitor_list=mlist)
param3 = ZOCPParameter(None, 0.3, 'param3', 'rw', None, 'f', params_list=plist, monitor_list=mlist)
print("removing 3")
param3.remove()
print("adding 4&5")
param4 = ZOCPParameter(None, 0.4, 'param4', 'rw', None, 'f', params_list=plist, monitor_list=mlist)
param5 = ZOCPParameter(None, 0.5, 'param5', 'rw', None, 'f', params_list=plist, monitor_list=mlist)
print(plist)
Loading