diff --git a/doc/demo_syntax.md b/doc/demo_syntax.md new file mode 100644 index 0000000..3ec05e6 --- /dev/null +++ b/doc/demo_syntax.md @@ -0,0 +1,315 @@ +## A SECoP Syntax for Demonstration + +I feel it is very hard to explain in a talk what we are talking +about when discussing the concepts of SECoP. Would it not be easier +with an example syntax and an example SEC node? That is why I +created a simple syntax, which is on one hand easily understandable +by humans and on the other hand fulfills the requirements for a real syntax. + +### An Example of an SEC Node with the Demo Syntax + +We have 2 main devices: sample temperature and magnetic field. + + > ts + ts=9.887 + + > mf + mf=5.1 + + +We have some additional devices, coil temperature of upper and lower coil. + + > tc1 + tc1=2.23 + + > tc2 + tc2=2.311 + +When we use the term "device" here, we do not mean a device in the sense of a +cryomagnet or a furnace. For SECoP, roughly any physical quantity which may +be of interest to the experimentalist, is its own device. + + +We can use wildcards to see the values of all devices at once. + + > * + ts=9.887 + mf=5.1 + tc1=2.23 + tc2=2.311 + label='Cryomagnet MX15; at 9.887 K; Persistent at 5.1 Tesla' + + +The "> " is not part of the syntax, it is just indicating the request. +A request is always one line. +A reply might contain several lines and always ends with an empty line. + +The last device here is a text displayed on the SE computer. It may not be +very useful, but it demonstrates that a device value may also be a text. + +A device has parameters. The main parameter is its value. The value parameter +is just omitted in the path. We can list the the parameters and its values +with the following command: + + > mf:* + mf=5.13 + mf:status=0 + mf:target=14.9 + mf:ramp=0.4 + mf:set_point=5.13 + mf:persistent_mode=0 + mf:switch_heater=1 + +If we want to change the field we have to set its target value: + + > mf:target=12 + mf:target=12.0 + + > mf:* + mf=5.13 + mf:status=1 + mf:target=12.0 + mf:ramp=0.4 + mf:set_point=5.13 + mf:persistent_mode=0 + mf:switch_heater=1 + +The status is indicating the state of the device. 0 means idle, 1 means busy +(running to the target). + +A parameter has properties. The 'value' property is implicit, its just +omitted in the path: + + > mf:status:* + mf:status=0 + mf:status:value_names=0:idle,1:busy,2:error + mf:status:type=int + mf:status:t=2016-08-23 14:55:45.254348 + +Please notice the property "value_names" indicating the meaning of the status +values. + + > mf:target:* + mf:target=12.0 + mf:target:writable=1 + mf:target:unit=T + mf:target:type=float + mf:target:t=2016-08-23 14:55:44.658749 + +The last property 't' is the timestamp. If the client want to record timestamps, +it can enable it for all device or parameter readings: + + > :reply_items=t + :reply_items=t + + > * + ts=9.867;t=2016-08-23 14:55:44.655862 + mf=5.13;t=2016-08-23 14:55:44.656032 + tc1=2.23;t=2016-08-23 14:55:44.656112 + tc2=2.311;t=2016-08-23 14:55:44.656147 + label='Cryomagnet MX15; at 9.867 K; Ramping at 5.13 Tesla';t=2016-08-23 14:55:44.656183 + +There is also a list command showing the devices (and parameters) without +values. I am not sure if we really need that, as we can just use a wildcard +read command and throw away the values. + + > !* + ts + mf + tc1 + tc2 + label + + > !ts:* + ts + ts:status + ts:target + ts:ramp + ts:use_ramp + ts:set_point + ts:heater_power + ts:raw_sensor + +The property "meaning" indicates the meaning of the most important devices. +We can list all the devices which have a "meaning" property + + > *::meaning + ts::meaning=temperature + mf::meaning=magnetic_field + +> Markus: We have more things to tell here. + + +As a last example: the ultimate command to get everything: + + > *:*:* + ts=9.887 + ts::meaning=temperature + ts::unit=K + ts::description='VTI sensor (15 Tesla magnet)\ncalibration: X28611' + ts::type=float + ts::t=2016-08-23 14:55:44.655862 + ts:status=0 + ts:status:type=int + ts:status:t=2016-08-23 14:55:44.655946 + ts:target=10.0 + ts:target:writable=1 + ts:target:unit=K + ts:target:type=float + ts:target:t=2016-08-23 14:55:44.655959 + ts:ramp=0.0 + ts:ramp:writable=1 + ts:ramp:unit=K/min + ts:ramp:type=float + ts:ramp:t=2016-08-23 14:55:44.655972 + ts:use_ramp=0 + ts:use_ramp:type=int + ts:use_ramp:t=2016-08-23 14:55:44.655984 + ts:set_point=10.0 + ts:set_point:unit=K + ts:set_point:type=float + ts:set_point:t=2016-08-23 14:55:44.655995 + ts:heater_power=0.154 + ts:heater_power:unit=W + ts:heater_power:type=float + ts:heater_power:t=2016-08-23 14:55:44.656006 + ts:raw_sensor=1876.3 + ts:raw_sensor:unit=Ohm + ts:raw_sensor:type=float + ts:raw_sensor:t=2016-08-23 14:55:44.656018 + mf=5.13 + mf::meaning=magnetic_field + mf::unit=T + mf::description=magnetic field (15 Tesla magnet) + mf::type=float + mf::t=2016-08-23 14:55:44.656032 + mf:status=0 + mf:status:type=int + mf:status:t=2016-08-23 14:55:44.656044 + mf:target=12.0 + mf:target:writable=1 + mf:target:unit=T + mf:target:type=float + mf:target:t=2016-08-23 14:55:44.658749 + mf:ramp=0.4 + mf:ramp:writable=1 + mf:ramp:unit=T/min + mf:ramp:type=float + mf:ramp:t=2016-08-23 14:55:44.656066 + mf:set_point=5.13 + mf:set_point:unit=T + mf:set_point:type=float + mf:set_point:t=2016-08-23 14:55:44.656077 + mf:persistent_mode=0 + mf:persistent_mode:type=int + mf:persistent_mode:t=2016-08-23 14:55:44.656088 + mf:switch_heater=1 + mf:switch_heater:type=int + mf:switch_heater:t=2016-08-23 14:55:44.656099 + tc1=2.23 + tc1::unit=K + tc1::description='top coil (15 Tesla magnet)\ncalibration: X30906' + tc1::type=float + tc1::t=2016-08-23 14:55:44.656112 + tc1:status=0 + tc1:status:type=int + tc1:status:t=2016-08-23 14:55:44.656123 + tc1:raw_sensor=5434.0 + tc1:raw_sensor:unit=Ohm + tc1:raw_sensor:type=float + tc1:raw_sensor:t=2016-08-23 14:55:44.656134 + tc2=2.311 + tc2::unit=K + tc2::description='bottom coil (15 Tesla magnet)\ncalibration: C103' + tc2::type=float + tc2::t=2016-08-23 14:55:44.656147 + tc2:status=0 + tc2:status:type=int + tc2:status:t=2016-08-23 14:55:44.656159 + tc2:raw_sensor=4834.5 + tc2:raw_sensor:unit=Ohm + tc2:raw_sensor:type=float + tc2:raw_sensor:t=2016-08-23 14:55:44.656169 + label='Cryomagnet MX15; Ramping' + label::writable=1 + label::type=string + label::t=2016-08-23 14:55:44.656183 + .:reply_items=t + .:reply_items:writable=1 + .:reply_items:type=string + .:reply_items:t=2016-08-23 14:55:44.659617 + .:compact_output=0 + .:compact_output:writable=1 + .:compact_output:type=int + .:compact_output:t=2016-08-23 14:55:44.656219 + + +The last device '.' is a dummy device to hold the parameters of a client +connection. Changing these parameters must not affect other client connections. +The experimental parameter compact_output is for compressing the result of +wildcard requests: unchanged device and parameter names are omitted. + + + > :compact_output=1 + .:compact_output=1 + + > *:*:* + ts=9.887 + ::meaning=temperature + ::unit=K + ::description='VTI sensor (15 Tesla magnet)\ncalibration: X28611' + ::type=float + ::t=2016-08-23 15:04:55.180514 + :status=0 + ::type=int + ::t=2016-08-23 15:04:55.180587 + :target=10.0 + ::writable=1 + ::unit=K + ::type=float + ::t=2016-08-23 15:04:55.180594 + :ramp=0.0 + ::writable=1 + ::unit=K/min + ::type=float + ::t=2016-08-23 15:04:55.180599 + :use_ramp=0 + ::type=int + ::t=2016-08-23 15:04:55.180604 + :set_point=10.0 + ::unit=K + ::type=float + ::t=2016-08-23 15:04:55.180609 + :heater_power=0.154 + ::unit=W + ::type=float + ::t=2016-08-23 15:04:55.180615 + :raw_sensor=1876.3 + ::unit=Ohm + ::type=float + ::t=2016-08-23 15:04:55.180620 + mf=5.13 + ::meaning=magnetic_field + ::unit=T + ::description=magnetic field (15 Tesla magnet) + ::type=float + ::t=2016-08-23 15:04:55.180626 + :status=0 + ::type=int + ::t=2016-08-23 15:04:55.180632 + :target=14.9 + ::writable=1 + ::unit=T + ::type=float + ::t=2016-08-23 15:04:55.180637 + :ramp=0.4 + ::writable=1 + ::unit=T/min + ::type=float + ::t=2016-08-23 15:04:55.180642 + :set_point=5.13 + ::unit=T + + ... + + diff --git a/doc/demo_syntax_mapping.md b/doc/demo_syntax_mapping.md new file mode 100644 index 0000000..705a87b --- /dev/null +++ b/doc/demo_syntax_mapping.md @@ -0,0 +1,285 @@ +## A SECoP Syntax for Demonstration + +### Mapping of Messages + +Please remind: replies always end with en empty line, which means that there are +2 newline characters at the end of each reply. To emphasize that, in this document +a bare '_' is shown as a replacement for the closing empty line. + +#### ListDevices + + > !* + + + ... + + _ + +#### ListDeviceParams + + > !:* + : + : + ... + : + _ + +#### ReadRequest + + > : + := + _ + +Or, in case it is combined with timestamp (may be also sigma, unit ...). See also +below under "client parameters" / "reply_items". + + > : + :=;t= + _ + > : + :=;t=;s= + _ + > : + :=;t=;s=;unit= + _ + +#### ReadPropertiesOfAllDevices +remark: for shortness the name of the value property and the preceding ':' is omitted + + > *:*:* + := + ::= + ... + ::= + _ + +#### ReadPropertiesOfADevice +means read properties of all parameters of a device) + + > :*:* + := + ::= + ... + ::= + _ + +#### ReadPropertiesOfAParameter + + > ::* + := + ::= + ... + ::= + _ + +#### ReadSpecificProperty + + > :: + ::= + _ + +In case you want the value only, and not the timestamp etc, '.' is a placeholder for the +value property + + > ::. + ::.= + _ + +#### ReadValueNotOlderThan + +Instead of this special Request, I propose to make a client parameter "max_age", +which specifies in general how old values may be to be returned from cache. + +#### Write + +remark: writes to other than the 'value' property are not allowed. If anyone sees a +reasonable need to make writeable properties, a WriteProperty Request/Reply should +be discussed + + > := + := + _ + +#### Command + +If we want to distinguish between a write request and a command, we need also a +different syntax. It is to decide, how arguments may be structured / typed. +Should a command also send back a "return value"? + + > : + : + _ + +#### Error replies + +Error reply format: + + ~~ + _ + +The error-specifier should be predefined identifer. + + > tempature:target + ~NoSuchCommand~ tempature + _ + + > temperature:taget + ~NoSuchParameter~ temperature:taget + _ + +#### FeatureListRequest + +Instead of an extra FeatureListRequest message, I propose to do have a device, +which contains some SEC node properties, with information about the SEC node, +and client parameters, which can be set by the ECS for optional +features. Remind that internally the SEC Node has to store the client parameters +separately for every client. + +#### SEC Node properties + +You are welcome to propose better names: + + > ::implements_timestamps + ::implements_timestamps=1 + _ + > ::implements_async_communication + ::implements_async_communication=1 + _ + +The maximum time delay for a response from the SEC Node: + + > ::reply_timeout=10 + ::reply_timeout=10 + _ + +SEC Node properties might be omitted for the default behaviour. + +#### Client parameters + +Enable transmission of timestamp and sigma with every value + + > :reply_items=t,s + :reply_items=t,s + _ + +If a requested property is not present on a parameter, it is just omitted in the reply. + +The reply_items parameter might be not be present, when the SEC node does not implement +timestamps and similar. + +Update timeout (see Update message) + + > :update_timeout=10 + :update_timeout=10 + +#### SubscribeToAsyncData + +In different flavors: all parameters and all devices: + + > +*:* + . + ... + . + _ + +Only the values of all devices: + + > +* + + + ... + + + _ + +All parameters of one device: + + > +:* + +: + ... + +: + _ + +I think we need no special subscriptions to properties, as :reply_items should be +recognized by the updates. All other properties are not supposed to be changed during +an experiment (we might discuss this). + +If an Unsubscribe Message would be implemented, it could be start with a '-' + +#### Update + +In order to stick to a strict request / reply mechanism I propose instead of a real +asynchronous communication to have an UpdateRequest. The reply of an UpdateRequest +might happen delayed. + +- it replies immediately with a list of all subscripted updates, if some happend since + the last UpdateRequest +- if nothing has changed, the UpdateReply is sent as soon as any update happens +- if nothing happens within the time specified by :update_timeout + an empty reply is returned. + +If a client detects no reply within :update_timeout plus ::reply_timeout, +it can assume the the SEC Node is dead. + +The UpdateRequest may be just an empty line (my favorite) or, if you prefer an +question mark: + + > ? + = + .= + _ + +With no update during :update_timeout seconds, the reply would be + + > ? + _ + + +#### Additional Messages + +I list here some additional messages, which could be useful, but wich were not yet +identified as special messages. They all follow the same syntax, which means that +it is probably no extra effort for the implementation. + +Interestingly, we have defined ReadPropertiesOfAllDevices, but not Read a specific +property of all parameters. At least reading the value properties is useful: + + > *:* + +List all device classes (assuming a device property "class") + + > *::class + ::class= + ::class= + ... + ::class= + _ + +The property meaning is for saying: this device is important for the experimentalist, +and has the indicated meaning. It might be used by the ECS to skip devices, which +are of no interest for non experts. Example: + + > *::meaning + ts::meaning=sample temperature + mf::meaning=magnetic field + _ + +We might find other useful messages, which can be implemented without any additional +syntax. + + +#### A possible syntax extension: + +Allow a comma separated list for path items: + + > ts,mf + ts=1.65 + mf=5.13 + _ + + > ts::.,t,s + ts= + ts::t= + ts::s= + _ + +If somebody does not like the :reply_items mechanism, we could us the latter example +as alternative for reading values together with timestamp and sigma. diff --git a/src/demo_syntax/cmd_lineserver.py b/src/demo_syntax/cmd_lineserver.py new file mode 100644 index 0000000..e5288de --- /dev/null +++ b/src/demo_syntax/cmd_lineserver.py @@ -0,0 +1,38 @@ +import fileinput + +class LineHandler(): + + def __init__(self, *args, **kwargs): + pass + + def send_line(self, line): + print " ", line + + def handle_line(self, line): + ''' + test: simple echo handler + ''' + self.send_line("> " + line) + +class LineServer(): + + def __init__(self, isfile, lineHandlerClass): + self.lineHandlerClass = lineHandlerClass + self.isfile = isfile + + def loop(self): + handler = self.lineHandlerClass() + while True: + try: + if self.isfile: + line = raw_input("") + print "> "+line + else: + line = raw_input("> ") + except EOFError: + return + handler.handle_line(line) + +if __name__ == "__main__": + server = LineServer("localhost", 9999, LineHandler) + server.loop() diff --git a/src/demo_syntax/demo_server.py b/src/demo_syntax/demo_server.py new file mode 100644 index 0000000..d219a1d --- /dev/null +++ b/src/demo_syntax/demo_server.py @@ -0,0 +1,312 @@ +# SECoP demo syntax simulation +# +# Author: Markus Zolliker +# +# Input from a file for test purposes: +# +# python -m demo_server f < test.txt +# +# Input from commandline: +# +# python -m demo_server +# +# Input from TCP/IP connections: +# +# python -m demo_server + + +import json +from collections import OrderedDict +from fnmatch import fnmatch +from datetime import datetime + +import sys +if len(sys.argv) <= 1: + port = 0 + import cmd_lineserver as lineserver +elif sys.argv[1] == "f": + port = 1 + import cmd_lineserver as lineserver +else: + port = int(sys.argv[1]) + import tcp_lineserver as lineserver + + +class SecopError(Exception): + def __init__(self, message, text): + Exception.__init__(self, message, text) + +class UnknownDeviceError(SecopError): + def __init__(self, message, args): + SecopError.__init__(self, "NoSuchDevice", args[0]) + +class UnknownParamError(SecopError): + def __init__(self, message, args): + SecopError.__init__(self, "NoSuchParam", "%s:%s" % args) + +class UnknownPropError(SecopError): + def __init__(self, message, args): + SecopError.__init__(self, "NoSuchProperty", "%s:%s:%s" % args) + +class SyntaxError(SecopError): + def __init__(self, message): + SecopError.__init__(self, "SyntaxError", message) + +class OtherError(SecopError): + def __init__(self, message): + SecopError.__init__(self, "OtherError", message) + + +def encode(input): + inp = str(input) + enc = repr(inp) + if inp.find(';') >= 0 or inp != enc[1:-1]: + return enc + return inp + +def gettype(obj): + typ = str(type(obj).__name__) + if typ == "unicode": + typ = "string" + return typ + +def wildcard(dictionary, pattern): + list = [] + if pattern == "": + pattern = "." + for p in pattern.split(","): + if p[-1] == "*": + if p[:-1].find("*") >= 0: + raise SyntaxError("illegal wildcard pattern %s" % p) + for key in dictionary: + if key.startswith(p[:-1]): + list.append(key) + elif p in dictionary: + list.append(p) + else: + raise KeyError(pattern) + return list + + +class SecopClientProps(object): + def __init__(self): + self.reply_items = {".":"t", "writable":1} + self.compact_output = {".":0, "writable":1} + +class SecopLineHandler(lineserver.LineHandler): + def __init__(self, *args, **kwargs): + lineserver.LineHandler.__init__(self, *args, **kwargs) + self.props = SecopClientProps() + + def handle_line(self, msg): + try: + if msg[-1:] == "\r": # strip CR at end (for CRLF) + msg=msg[:-1] + if msg[:1] == "+": + self.subscribe(msg[1:].split(":")) + elif msg[:1] == "-": + self.unsubscribe(msg[1:].split(":")) + elif msg[:1] == "!": + self.list(msg[1:]) + else: + j = msg.find("=") + if j >= 0: + self.write(msg[0:j], msg[j+1:]) + else: + self.read(msg) + except SecopError as e: + self.send_line("~%s~ %s" % (e.args[0], e.args[1])) + self.send_line("") + + + def get_device(self, d): + if d == "": + d = "." + if d == ".": + return self.props.__dict__ + try: + return secNodeDict[d] + except KeyError: + raise UnknownDeviceError("",(d)) + + def get_param(self, d, p): + if p == "": + p = "." + try: + return self.get_device(d)[p] + except KeyError: + raise UnknownParamError("",(d,p)) + + def get_prop(self, d, p, y): + if y == "": + y = "." + try: + paramDict = self.get_param(d, p) + return (paramDict, paramDict[y]) + except KeyError: + raise UnknownPropertyError("",(d,p,y)) + + def clear_output_path(self): + # used for compressing only + self.outpath = [".", ".", "."] + try: + self.compact = self.props.compact_output["."] != 0 + except KeyError: + self.compact = False + + def output_path(self, d, p=".", y="."): + # compose path from arguments. compress if compact is True + if d == self.outpath[0]: + msg = ":" + else: + msg = d + ":" + if self.compact: + self.outpath[0] = d + self.outpath[1] = "." + self.outpath[2] = "." + if p == self.outpath[1]: + msg += ":" + else: + msg += p + ":" + if self.compact: + self.outpath[1] = p + self.outpath[2] = "." + if y == "" or y == self.outpath[2]: + while msg[-1:] == ":": + msg = msg[:-1] + else: + msg += y + if self.compact: + self.outpath[2] = y + return msg + + def write(self, pathArg, value): + self.clear_output_path() + path = pathArg.split(":") + while len(path) < 3: + path.append("") + d,p,y = path + parDict = self.get_param(d, p) + if (y != "." and y != "") or not parDict.get("writable", 0): + self.send_line("? %s is not writable" % self.output_path(d,p,y)) + typ = type(parDict["."]) + try: + val = (typ)(value) + except ValueError: + raise SyntaxError("can not convert '%s' to %s" % (value, gettype(value))) + parDict["."] = val + parDict["t"] = datetime.utcnow() + self.send_line(self.output_path(d, p, ".") + "=" + encode(parDict["."])) + + + def read(self, pathArg): + self.clear_output_path() + path = pathArg.split(":") + if len(path) > 3: + raise SyntaxError("path may only contain 3 elements") + while len(path) < 3: + path.append("") + + # first collect a list of matched properties + list = [] + try: + devList = wildcard(secNodeDict, path[0]) + except KeyError as e: + raise UnknownDeviceError("", (e.message)) + for d in devList: + devDict = secNodeDict[d] + try: + parList = wildcard(devDict, path[1]) + except KeyError as e: + raise UnknownParamError("", (d, e.message)) + for p in parList: + parDict = devDict[p] + try: + propList = wildcard(parDict, path[2]) + except KeyError as e: + raise UnknownPropError("", (d, p, e.message)) + for y in propList: + list.append((d, p, y)) + + # then, if no error happened, write out the messages + try: + replyitems = self.props.reply_items["."] + replyitems = replyitems.split(",") + except KeyError: + replyitems = [] + for item in list: + d, p, y = item + paramDict = secNodeDict[d][p] + if path[2] == "": + msg = self.output_path(d, p, "") + "=" + encode(paramDict["."]) + for y in replyitems: + if y == ".": continue # do not show the value twice + try: + msg += ";" + y + "=" + encode(paramDict[y]) + except KeyError: + pass + else: + msg = self.output_path(d, p, y) + "=" + encode(paramDict[y]) + self.send_line(msg) + + def list(self, pathArg): + self.clear_output_path() + path = pathArg.split(":") + # first collect a list of matched items + list = [] + try: + devList = wildcard(secNodeDict, path[0]) + except KeyError as e: + raise UnknownDeviceError("", (e.message)) + for d in devList: + devDict = secNodeDict[d] + if len(path) == 1: + list.append((d, ".", ".")) + else: + try: + parList = wildcard(devDict, path[1]) + except KeyError as e: + raise UnknownParamError("", (d, e.message)) + for p in parList: + parDict = devDict[p] + if len(path) == 2: + list.append((d, p, ".")) + else: + try: + propList = wildcard(parDict, path[2]) + except KeyError as e: + raise UnknownPropError("", (d, p, e.message)) + for y in propList: + list.append((d, p, y)) + + # then, if no error happened, write out the items + for item in list: + d, p, y = item + self.send_line(self.output_path(d, p, y)) + + def subscribe(self, pathArg): + raise OtherError("subscribe unimplemented") + + def unsubscribe(self, pathArg): + raise OtherError("unsubscribe unimplemented") + +if port <= 1: + server = lineserver.LineServer(port, SecopLineHandler) +else: + server = lineserver.LineServer("localhost", port, SecopLineHandler) + + +secNodeDict=json.load(open("secnode.json", "r"), object_pairs_hook=OrderedDict) +#json.dump(secNodeDict, open("secnode_out.json", "w"), indent=2, separators=(",",":")) +for d in secNodeDict: + devDict = secNodeDict[d] + for p in devDict: + parDict = devDict[p] + try: + parDict["type"] = gettype(parDict["."]) + except KeyError: + print d, p, " no '.' (value) property" + continue + parDict["t"] = datetime.utcnow() + +server.loop() diff --git a/src/demo_syntax/secnode.json b/src/demo_syntax/secnode.json new file mode 100644 index 0000000..fbefaf7 --- /dev/null +++ b/src/demo_syntax/secnode.json @@ -0,0 +1,104 @@ +{ + "ts":{ + ".":{ + ".":9.887, + "meaning":"temperature", + "unit":"K", + "description":"VTI sensor (15 Tesla magnet)\ncalibration: X28611" + }, + "status":{ + ".":0 + }, + "target":{ + ".":10.0, + "writable":1, + "unit":"K" + }, + "ramp":{ + ".":0.0, + "writable":1, + "unit":"K/min" + }, + "use_ramp":{ + ".":0 + }, + "set_point":{ + ".":10.0, + "unit":"K" + }, + "heater_power":{ + ".":0.154, + "unit":"W" + }, + "raw_sensor":{ + ".":1876.3, + "unit":"Ohm" + } + }, + "mf":{ + ".":{ + ".":5.13, + "meaning":"magnetic_field", + "unit":"T", + "description":"magnetic field (15 Tesla magnet)" + }, + "status":{ + ".":0, + "value_names":"0:idle,1:busy,2:error" + }, + "target":{ + ".":14.9, + "writable":1, + "unit":"T" + }, + "ramp":{ + ".":0.4, + "writable":1, + "unit":"T/min" + }, + "set_point":{ + ".":5.13, + "unit":"T" + }, + "persistent_mode":{ + ".":0 + }, + "switch_heater":{ + ".":1 + } + }, + "tc1":{ + ".":{ + ".":2.23, + "unit":"K", + "description":"top coil (15 Tesla magnet)\ncalibration: X30906" + }, + "status":{ + ".":0 + }, + "raw_sensor":{ + ".":5434.0, + "unit":"Ohm" + } + }, + "tc2":{ + ".":{ + ".":2.311, + "unit":"K", + "description":"bottom coil (15 Tesla magnet)\ncalibration: C103" + }, + "status":{ + ".":0 + }, + "raw_sensor":{ + ".":4834.5, + "unit":"Ohm" + } + }, + "label":{ + ".":{ + ".": "test;test", + "writable": 1 + } + } +} diff --git a/src/demo_syntax/tcp_lineserver.py b/src/demo_syntax/tcp_lineserver.py new file mode 100644 index 0000000..f25ec5d --- /dev/null +++ b/src/demo_syntax/tcp_lineserver.py @@ -0,0 +1,59 @@ +import asyncore +import socket + +class LineHandler(asyncore.dispatcher_with_send): + + def __init__(self, sock): + self.buffer = "" + asyncore.dispatcher_with_send.__init__(self, sock) + self.crlf = 0 + + def handle_read(self): + data = self.recv(8192) + if data: + parts = data.split("\n") + if len(parts) == 1: + self.buffer += data + else: + self.handle_line(self.buffer + parts[0]) + for part in parts[1:-1]: + if part[-1] == "\r": + self.crlf = True + part = part[:-1] + else: + self.crlf = False + self.handle_line(part) + self.buffer = parts[-1] + + def send_line(self, line): + self.send(line + ("\r\n" if self.crlf else "\n")) + + def handle_line(self, line): + ''' + test: simple echo handler + ''' + self.send_line("> " + line) + +class LineServer(asyncore.dispatcher): + + def __init__(self, host, port, lineHandlerClass): + asyncore.dispatcher.__init__(self) + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.set_reuse_addr() + self.bind((host, port)) + self.listen(5) + self.lineHandlerClass = lineHandlerClass + + def handle_accept(self): + pair = self.accept() + if pair is not None: + sock, addr = pair + print "Incoming connection from %s" % repr(addr) + handler = self.lineHandlerClass(sock) + + def loop(self): + asyncore.loop() + +if __name__ == "__main__": + server = LineServer("localhost", 9999, LineHandler) + server.loop() diff --git a/src/demo_syntax/test_requests.txt b/src/demo_syntax/test_requests.txt new file mode 100644 index 0000000..88b16e6 --- /dev/null +++ b/src/demo_syntax/test_requests.txt @@ -0,0 +1,13 @@ +:reply_items= +ts +mf +tc1 +* +mf:* +mf:target=12 +mf:* +mf:target:* +:reply_items=t +* +!* +*:*:*