picmd is a framework for creating applications that request and respond to AT commands using serial communication.
Its main purpose is for use with Raspberry PI.
Create an instance of PiCmd
with the create
method and register the command handler with the handler
decorator.
Start the application with the run
method and wait for a command request.
from picmd import PiCmd
import time
app = PiCmd.create('/dev/serial0')
@app.handler(0x01)
def greeting_handler() -> str:
return 'hello world'
@app.handler(0x02)
def file_receive_handler(data: bytes):
with open('./tmp/received-%s.bin' % int(time.time()), 'wb') as f:
f.write(data)
app.run()
Hexadecimal data starting with AT*PIC=
and ending with CRLF
.
The command type is specified by the first 8 bits.
The command parameter size is represented by the second 16 bits. Then there is the content of the command parameters.
The check parity is the XOR of the values from command type to command parameter.
AT*PIC=\x01\x04\x00\x01\x00\x00\x00\x04\r\n
<-----><--><------><--------------><--><-->
\ \ \ \ \ \_ command end delimiter (CRLF)
\ \ \ \ \_ check parity
\ \ \ \_ command data (The length changes depending on the value of "command data size")
\ \ \_ command data size (max 0xffff)
\ \_ command (max 0xff)
\_ command start prefix
Hexadecimal data starting with *PIC:
and ending with CRLF
.
The response status is specified by the first 8 bits.
The response data size is represented by the second 16 bits. Then there is the content of the response datas.
The check parity is the XOR of the values from response status to response data.
*PIC:\x01\x04\x00\x01\x00\x00\x00\x04\r\nOK\r\n
<---><--><------><--------------><--><-------->
\ \ \ \ \ \_ response end delimiter
\ \ \ \ \_ check parity
\ \ \ \_ response data (The length changes depending on the value of "response data size")
\ \ \_ response data size (max 0xffff)
\ \_ response status (0x01)
\_ response start prefix
*PIC:\x07\x04\x00\x01\x00\x00\x00\x02\r\nERROR\r\n
<---><--><------><--------------><--><----------->
\ \ \ \ \ \_ response end delimiter
\ \ \ \ \_ check parity
\ \ \ \_ response data (The length changes depending on the value of "response data size")
\ \ \_ response data size (max 0xffff)
\ \_ response status (values from 0x02 to 0xff)
\_ response start prefix
status | description |
---|---|
0x01 | No error |
0x02 | Invalid command format error |
0x03 | Invalid parity error |
0x04 | Command not found error |
0x05 | Invalid data length error |
0x06 | Command fail error |
Sends the following command.
AT\r\n
If the next message is received in the response, the communication confirmation is successful.
\r\n
OK
\r\n
The command handler function receives the byte data of the command parameter in the first argument data
and the size of the command parameter in the second argument size
. It is also possible to omit either or both of these.
You can take a dependency set by the provide
function (see later) as an argument.
An example of a case where you want to receive both data and size.
@app.handler(0x01)
def handler(data: bytes, size: int):
....
An example of receiving data only.
@app.handler(0x01)
def handler(data: bytes):
....
Example of a case where both are not received.
@app.handler(0x01)
def handler():
....
An example of receiving data and dependencies.
@app.handler(0x01)
def handler(data: bytes, service1: Service1, service2: Service2):
....
Returns a value of type bool
, int
, float
,str
, or bytes
.
@app.handler(0x01)
def handler(data: bytes, size: int) -> Union[bool, int, float, str, bytes]:
....
return response_data
Returns nothing.
@app.handler(0x01)
def handler(data: bytes, size: int):
....
# nothing return
Raise an exception with the status_code
attribute.
If you want to return error message etc. as response data, add description
attribute.
class DomainException(Exception):
status_code = 0xff
description = 'any error message'
@app.handler(0x01)
def handler(data: bytes, size: int):
....
raise DomainException
Take the serial port path as an argument and create an instance.
Decorator that takes a command type as an argument and registers it as a handler.
Specifies the dependency to pass to the handler by dict. The handler will receive the same arguments as the key name.
for example.
app.provide({
'service1': service1,
'service2': service2
})
Import the handlers registered in the HandlerRegister.
Start accepting and responding to commands.
It is used to register handlers to be imported into PiCmd.
Decorator that takes a command type as an argument and registers it as a handler.
The libraries for the client are as follows.
- node-picmd (node.js)
Added HandlerRegister feature.
Fixed a response bug of the PING command.
Added a feature to confirm communication.
Added the feature to provide dependency.
Added support for omitting the arguments of the handler function.
Change the data format of the protocol.
Change request and resopnse prefix.
Initial release.
MIT