Archive for 4月, 2014

XML-RPC 笔记

XML-RPC 编码是个坑,只支持 ASCII 字符串(或二进制缓冲块)。为了安全,只能用 xmlrpclib.Binary 把数据包装一下了。

 
Python 2.7.5 (default, May 15 2013, 22:43:36) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> from SimpleXMLRPCServer import SimpleXMLRPCServer
>>> import xmlrpclib
>>> def a():
	return xmlrpclib.Binary({'a':1})

>>> server = SimpleXMLRPCServer(("localhost", 8000))
>>> print "Listening on port 8000..."
Listening on port 8000...
>>> server.register_function(python_logo, 'python_logo')

Traceback (most recent call last):
  File "", line 1, in 
    server.register_function(python_logo, 'python_logo')
NameError: name 'python_logo' is not defined
>>> server.register_function(a, 'a')
>>> server.serve_forever()
127.0.0.1 - - [22/Apr/2014 22:36:32] "POST / HTTP/1.1" 200 -

Traceback (most recent call last):
  File "", line 1, in 
    server.serve_forever()
  File "C:\Python27\lib\SocketServer.py", line 236, in serve_forever
    poll_interval)
  File "C:\Python27\lib\SocketServer.py", line 155, in _eintr_retry
    return func(*args)
KeyboardInterrupt

>>> import json
>>> def a():
	return xmlrpclib.Binary(json.dumps({'a':1}))

>>> server.register_function(a, 'a')
>>> server.serve_forever()
127.0.0.1 - - [22/Apr/2014 22:37:27] "POST / HTTP/1.1" 200 -
127.0.0.1 - - [22/Apr/2014 22:37:39] "POST / HTTP/1.1" 200 -
127.0.0.1 - - [22/Apr/2014 22:38:04] "POST / HTTP/1.1" 200 -
 
Python 2.7.5 (default, May 15 2013, 22:43:36) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import xmlrpclib
>>> proxy = xmlrpclib.ServerProxy("http://localhost:8000/")
>>> proxy.a()

Traceback (most recent call last):
  File "", line 1, in 
    proxy.a()
  File "C:\Python27\lib\xmlrpclib.py", line 1224, in __call__
    return self.__send(self.__name, args)
  File "C:\Python27\lib\xmlrpclib.py", line 1578, in __request
    verbose=self.__verbose
  File "C:\Python27\lib\xmlrpclib.py", line 1264, in request
    return self.single_request(host, handler, request_body, verbose)
  File "C:\Python27\lib\xmlrpclib.py", line 1297, in single_request
    return self.parse_response(response)
  File "C:\Python27\lib\xmlrpclib.py", line 1473, in parse_response
    return u.close()
  File "C:\Python27\lib\xmlrpclib.py", line 793, in close
    raise Fault(**self._stack[0])
Fault: :must be string or buffer, not dict">
>>> proxy.a()

>>> proxy.a().data
'{"a": 1}'
>>> import json
>>> json.loads(proxy.a().data)
{u'a': 1}
>>> 

另一种注册方法:
http://soft.zdnet.com.cn/software_zone/2008/0527/887034.shtml

 

import SimpleXMLRPCServer

#定义自己的CMS类
class MyCMS:
    def getVersion(self):#向外公开版本的方法
        return "Powerd By python 0.1a"

cms = MyCMS()
server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 8888))
server.register_instance(cms)

print "Listening on port 8888"
server.serve_forever()#服务器执行,并监听8888端口


import xmlrpclib

server = xmlrpclib.ServerProxy("http://localhost:8888";)

version = server.getVersion()

print "version:"+version

参考:
https://docs.python.org/2/library/xmlrpclib.html#fault-objects

No comment »

lxml 笔记

蜘蛛之前用的pyquery做采集,发现对非正常的网页并不适用,正则表达式更是不能用,目标网站网页乱的一团乱麻,打算重新用lxml的迭代解析功能来做吧。

 
Python 2.7.3 (default, Apr 10 2012, 23:31:26) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import lxml

Traceback (most recent call last):
  File "", line 1, in 
    import lxml
ImportError: No module named lxml
>>> import lxml
>>> class EchoTarget(object):
	def start(self, tag, attrib):
		print("start %s %r" % (tag, dict(attrib)))
		print attrib
	def end(self, tag):
		print("end %s" % tag)
	def data(self, data):
		print("data %r" % data)
	def comment(self, text):
		print("comment %s" % text)
	def close(self):
		print("close")
		return "closed!"

	
>>> from lxml import etree
>>> from io import StringIO, BytesIO

>>> parser = etree.XMLParser(target = EchoTarget())
>>> result = etree.XML("sometext",parser)
close

Traceback (most recent call last):
  File "", line 1, in 
    result = etree.XML("sometext",parser)
  File "lxml.etree.pyx", line 2723, in lxml.etree.XML (src/lxml/lxml.etree.c:52448)
  File "parser.pxi", line 1573, in lxml.etree._parseMemoryDocument (src/lxml/lxml.etree.c:79932)
  File "parser.pxi", line 1452, in lxml.etree._parseDoc (src/lxml/lxml.etree.c:78774)
  File "parser.pxi", line 960, in lxml.etree._BaseParser._parseDoc (src/lxml/lxml.etree.c:75389)
  File "parsertarget.pxi", line 149, in lxml.etree._TargetParserContext._handleParseResultDoc (src/lxml/lxml.etree.c:86190)
  File "parser.pxi", line 585, in lxml.etree._raiseParseError (src/lxml/lxml.etree.c:71955)
XMLSyntaxError: AttValue: " or ' expected, line 1, column 15
>>> result = etree.XML("sometext",parser)
start element {'a': u'aaa', 'b': u'bbb'}
{'a': u'aaa', 'b': u'bbb'}
data u'some'
comment comment
data u'text'
end element
close
>>> result
'closed!'
>>> class EchoTarget(object):
	def start(self, tag, attrib):
		print attrib
		print("start %s %r" % (tag, dict(attrib)))
	def end(self, tag):
		print("end %s" % tag)
	def data(self, data):
		print("data %r" % data)
	def comment(self, text):
		print("comment %s" % text)
	def close(self):
		print("close")
		return "closed!"

	
>>> result = etree.XML("sometext",parser)
start element {'a': u'aaa', 'b': u'bbb'}
{'a': u'aaa', 'b': u'bbb'}
data u'some'
comment comment
data u'text'
end element
close
>>> class EchoTarget(object):
	def start(self, tag, attrib):
		print attrib
		print("start %s %r" % (tag, dict(attrib)))
	def end(self, tag):
		print("end %s" % tag)
	def data(self, data):
		print("data %r" % data)
	def comment(self, text):
		print("comment %s" % text)
	def close(self):
		print("close")
		return "closed!"

	
>>> parser = etree.XMLParser(target = EchoTarget())
>>> 
>>> result = etree.XML("sometext",parser)
{'a': u'aaa', 'b': u'bbb'}
start element {'a': u'aaa', 'b': u'bbb'}
data u'some'
comment comment
data u'text'
end element
close
>>> 

使用目标解析器方法
目标解析器方法对于熟悉 SAX 事件驱动代码的开发人员来说应该不陌生。目标解析器是可以实现以下方法的类:
start 在元素打开时触发。数据和元素的子元素仍不可用。
end 在元素关闭时触发。所有元素的子节点,包括文本节点,现在都是可用的。
data 触发文本子节点并访问该文本。
close 在解析完成后触发。
清单 2 演示了如何创建实现所需方法的目标解析器类(这里称为 TitleTarget)。这个解析器在一个内部列表(self.text)中收集 Title 元素的文本节点,并在到达 close() 方法后返回列表。
清单 2. 一个目标解析器,它返回 Title 标记的所有文本子节点的列表
class TitleTarget(object):
def __init__(self):
self.text = []
def start(self, tag, attrib):
self.is_title = True if tag == ‘Title’ else False
def end(self, tag):
pass
def data(self, data):
if self.is_title:
self.text.append(data.encode(‘utf-8’))
def close(self):
return self.text

parser = etree.XMLParser(target = TitleTarget(),recover=True)

# This and most other samples read in the Google copyright data
infile = ‘copyright.xml’

results = etree.parse(infile, parser)

# When iterated over, ‘results’ will contain the output from
# target parser’s close() method

out = open(‘titles.txt’, ‘w’)
out.write(‘\n’.join(results))
out.close()
在运行版权数据时,代码运行时间为 54 秒。目标解析可以实现合理的速度并且不会生成消耗内存的解析树,但是在数据中为所有元素触发事件。对于特别大型的文档,如果只对其中一些元素感兴趣,那么这种方法并不理想,就像在这个例子中一样。能否将处理限制到选择的标记并获得较好的性能呢?

In [7]: f = StringIO.StringIO(r”””
…:
…: Mark
…: http://diveintomark.org/
…:

…:Dive into history, 2009 edition
…:
…: tag:diveintomark.org,2009-03-27:/archives/20090327172042
…: 2009-03-27T21:56:07Z
…: 2009-03-27T17:20:42Z …:
…:
…:
…:

Putting an entire chapter on one page sounds ⑦
…: bloated, but consider this — my longest chapter so far
…: would be 75 printed pages, and it loads in under 5 seconds…
…: On dialup.

…: ⑧
…: “””)

http://lxml.de/parsing.html#parsers

The target parser interface

As in ElementTree, and similar to a SAX event handler, you can pass a target object to the parser:
>>> class EchoTarget(object):
… def start(self, tag, attrib):
… print(“start %s %r” % (tag, dict(attrib)))
… def end(self, tag):
… print(“end %s” % tag)
… def data(self, data):
… print(“data %r” % data)
… def comment(self, text):
… print(“comment %s” % text)
… def close(self):
… print(“close”)
… return “closed!”

>>> parser = etree.XMLParser(target = EchoTarget())

>>> result = etree.XML(“sometext“,
… parser)
start element {}
data u’some’
comment comment
data u’text’
end element
close

>>> print(result)
closed!
It is important for the .close() method to reset the parser target to a usable state, so that you can reuse the parser as often as you like:
>>> result = etree.XML(“sometext“,
… parser)
start element {}
data u’some’
comment comment
data u’text’
end element
close

>>> print(result)
closed!
Starting with lxml 2.3, the .close() method will also be called in the error case. This diverges(分歧) from the behaviour of ElementTree, but allows target objects to clean up their state in all situations, so that the parser can reuse them afterwards.
>>> class CollectorTarget(object):
… def __init__(self):
… self.events = []
… def start(self, tag, attrib):
… self.events.append(“start %s %r” % (tag, dict(attrib)))
… def end(self, tag):
… self.events.append(“end %s” % tag)
… def data(self, data):
… self.events.append(“data %r” % data)
… def comment(self, text):
… self.events.append(“comment %s” % text)
… def close(self):
… self.events.append(“close”)
… return “closed!”

>>> parser = etree.XMLParser(target = CollectorTarget())

>>> result = etree.XML(“some“,
… parser) # doctest: +ELLIPSIS
Traceback (most recent call last):

lxml.etree.XMLSyntaxError: Opening and ending tag mismatch…

>>> for event in parser.target.events:
… print(event)
start element {}
data u’some’
close
Note that the parser does not build a tree when using a parser target. The result of the parser run is whatever the target object returns from its .close() method. If you want to return an XML tree here, you have to create it programmatically in the target object. An example for a parser target that builds a tree is the TreeBuilder:
>>> parser = etree.XMLParser(target = etree.TreeBuilder())

>>> result = etree.XML(“sometext“,
… parser)

>>> print(result.tag)
element
>>> print(result[0].text)
comment

http://lxml.de/tutorial.html
http://www.ibm.com/developerworks/cn/xml/x-hiperfparse/index.html
http://sebug.net/paper/books/dive-into-python3/xml.html
http://alvinli1991.github.io/python/2013/11/12/python-xml-parser—elementtree/
http://pycoders-weekly-chinese.readthedocs.org/en/latest/issue6/processing-xml-in-python-with-element-tree.html

No comment »

NRF24L01 无线模块笔记

NRF24L01 收发地址

NRF24L01 模式

引脚      名称      引脚功能     描述
1         CE        数字输入     RX 或 TX 模式选择
2         CSN       数字输入     SPI 片选信号
3         SCK       数字输入     SPI 时钟
4         MOSI      数字输入     从 SPI 数据输入脚
5         MISO      数字输出     从 SPI 数据输出脚
6         IRQ       数字输出     可屏蔽中断脚
7         VDD       电源         电源(+3V)
8         VSS       电源         接地(0V)

nRF24L01 可以设置为以下几种主要的模式,
模式            PWR_UP         PRIM_RX              CE            FIFO 寄存器状态 
接收模式         1                1                  1             -
发送模式         1                0                  1             数据在 TX FIFO 寄存器中
发送模式         1                0                 1→0            停留在发送模式,直至数据发送完
待机模式 II      1                0                  1             TX FIFO 为空
待机模式 I       1                -                  0             无数据传输
掉电模式         0                -                  -             -

关于 nRF24L01 I/O 脚更详细的描述请参见下面的表。
nRF24L01 在不同模式下的引脚功能
引脚名称         方向        发送模式       接收模式     待机模式    掉电模式 
CE              输入         高电平>10us     高电平       低电平     -  
CSN             输入         SPI 片选使能,低电平使能
SCK             输入         SPI 时钟
MOSI            输入         SPI 串行输入
MISO            三态输出     SPI 串行输出
IRQ             输出        中断,低电平使能

增强型的ShockBurstTM 模式 比 ShockBurstTM 模式多了个自动应答、自动重发功能。

nRF24L01 在接收模式下可以接收6 路不同通道的数据,每一个数据通道使用不同的地址,但
是共用相同的频道。也就是说6 个不同的nRF24L01 设置为发送模式后可以与同一个设置为接收模式的
nRF24L01 进行通讯,而设置为接收模式的nRF24L01 可以对这6 个发射端进行识别。数据通道0 是唯一
的一个可以配置为40 位自身地址的数据通道。1~5 数据通道都为8 位自身地址和32 位公用地址。所有的
数据通道都可以设置为增强型ShockBurst 模式。
nRF24L01 在确认收到数据后记录地址,并以此地址为目标地址发送应答信号。在发送端,数据通道0
被用做接收应答信号,因此,数据通道0 的接收地址要与发送端地址相等以确保接收到正确的应答信号。
见图5 选择地址举例。

增强型ShockBurstTM 发送模式:
1、 配置寄存器位PRIM_RX 为低
2、 当MCU 有数据要发送时,接收节点地址(TX_ADDR)和有效数据(TX_PLD)通过SPI 接口写入
nRF24L01。发送数据的长度以字节计数从MCU 写入TX FIFO。当CSN 为低时数据被不断的写入。
发送端发送完数据后,将通道0 设置为接收模式来接收应答信号,其接收地址(RX_ADDR_P0)与接
收端地址(TX_ADDR)相同。例:在图5 中数据通道5 的发送端(TX5)及接收端(RX)地址设置如下:
TX5:TX_ADDR=0xB3B4B5B605
TX5:RX_ADDR_P0=0xB3B4B5B605
RX:RX_ADDR_P5=0xB3B4B5B605
3、 设置CE 为高,启动发射。CE 高电平持续时间最小为10 us。
4、 nRF24L01 ShockBurstTM 模式:

限制:
单无线模块只能接受4个设备发送的信息(实际是6个,但是1个为自动应答使用,一个我打算作为默认广播地址使用,就只剩下4个了。其中除了自动应答使用的1个设备地址之外的5个设备的地址非8位部分必须相同。8位地址可以容纳255个设备。),单个设备只能有一个设备地址来接收其他设备发送的信息。
一般200个地址就足够用了,也就是把所有设备除了后8位全部设置成为一样的。

路由设计:

之前被nrf24文档里面的图给坑了,认为为了维持两个设备之间的无线通信必须双方都有一个接收地址设置成为一样的,造成单个nrf24只能和6个设备通信。实际上被那个图给误解了,发数据到另一个设备时只需要把发送地址设置为对方的接收地址(6个中的任意一个)就行,并不需要把自己的接收地址也设置为对方的接收地址(开启自动重发功能时需要将接收地址1设置为发送地址来接受ACK回应),这样设备通信数量的限制就基本上没有了。

文档相关资料:
2、 当MCU 有数据要发送时,接收节点地址(TX_ADDR)和有效数据(TX_PLD)通过SPI 接口写入
nRF24L01。发送数据的长度以字节计数从MCU 写入TX FIFO。当CSN 为低时数据被不断的写入。
发送端发送完数据后,将通道0 设置为接收模式来接收应答信号,其接收地址(RX_ADDR_P0)与接
收端地址(TX_ADDR)相同。
4、 接收到有效的数据包后(地址匹配、CRC 检验正确),数据存储在RX_FIFO 中,同时RX_DR 位
置高,并产生中断。状态寄存器中RX_P_NO 位显示数据是由哪个通道接收到的。

中继设备只需要保存

目前不考虑超过200各设备的情况,真要超过的话以后可以分组的方式来解决。
添加新设备方式:
主控直接和间接(通过已并网的设备中继)的向新设备默认地址广播探测新设备。广播信息如果经过中继的话会记录着中继信息。新设备收到后,会已新设备默认地址按原路径回复信息(包含设备唯一编号)。主控会根据收到的回应来确认找到几个新设备(通过设备唯一编号),各个设备有几条线路可以连接到主控。然后主控为新设备分配设备地址(以后设备地址就固定为这个了)并生成主路由和备用路由信息发给新设备,然后新设备启用收到的新设备地址及路由信息。

个别设备断网时重新入网办法:
< del datetime="2014-01-31T05:43:02+00:00">子设备在主路由信息无法连接主控时会将自身地址设置为本网的新设备默认地址,并循环使用几个主备路由信息来尝试连接到主控。连接到主控则更新路由信息。< /del>

主控在连接不到子设备时会以本网的新设备默认地址为接收地址直接和间接(通过已并网设备中继)的发送探测子设备信息,子设备要是收到则回复,主控等待一段时间根据收到的回复建立新的路由信息并发给子设备,子设备重新并网。

当然,如果主控实在是收不到回应则要么是子设备丢失了本网的地址段(网段非默认网段的情况),要么是之间连接中继或信道环境严重恶化,无法连接,或者子设备掉电或损坏。这些情况只能人工检查和修复了。

No comment »